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]
 2479async fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2480    init_test(cx, |_| {});
 2481
 2482    let mut cx = EditorTestContext::new(cx).await;
 2483
 2484    // For an empty selection, the preceding word fragment is deleted.
 2485    // For non-empty selections, only selected characters are deleted.
 2486    cx.set_state("onˇe two t«hreˇ»e four");
 2487    cx.update_editor(|editor, window, cx| {
 2488        editor.delete_to_previous_word_start(
 2489            &DeleteToPreviousWordStart {
 2490                ignore_newlines: false,
 2491                ignore_brackets: false,
 2492            },
 2493            window,
 2494            cx,
 2495        );
 2496    });
 2497    cx.assert_editor_state("ˇe two tˇe four");
 2498
 2499    cx.set_state("e tˇwo te «fˇ»our");
 2500    cx.update_editor(|editor, window, cx| {
 2501        editor.delete_to_next_word_end(
 2502            &DeleteToNextWordEnd {
 2503                ignore_newlines: false,
 2504                ignore_brackets: false,
 2505            },
 2506            window,
 2507            cx,
 2508        );
 2509    });
 2510    cx.assert_editor_state("e tˇ te ˇour");
 2511}
 2512
 2513#[gpui::test]
 2514async fn test_delete_whitespaces(cx: &mut TestAppContext) {
 2515    init_test(cx, |_| {});
 2516
 2517    let mut cx = EditorTestContext::new(cx).await;
 2518
 2519    cx.set_state("here is some text    ˇwith a space");
 2520    cx.update_editor(|editor, window, cx| {
 2521        editor.delete_to_previous_word_start(
 2522            &DeleteToPreviousWordStart {
 2523                ignore_newlines: false,
 2524                ignore_brackets: true,
 2525            },
 2526            window,
 2527            cx,
 2528        );
 2529    });
 2530    // Continuous whitespace sequences are removed entirely, words behind them are not affected by the deletion action.
 2531    cx.assert_editor_state("here is some textˇwith a space");
 2532
 2533    cx.set_state("here is some text    ˇwith a space");
 2534    cx.update_editor(|editor, window, cx| {
 2535        editor.delete_to_previous_word_start(
 2536            &DeleteToPreviousWordStart {
 2537                ignore_newlines: false,
 2538                ignore_brackets: false,
 2539            },
 2540            window,
 2541            cx,
 2542        );
 2543    });
 2544    cx.assert_editor_state("here is some textˇwith a space");
 2545
 2546    cx.set_state("here is some textˇ    with a space");
 2547    cx.update_editor(|editor, window, cx| {
 2548        editor.delete_to_next_word_end(
 2549            &DeleteToNextWordEnd {
 2550                ignore_newlines: false,
 2551                ignore_brackets: true,
 2552            },
 2553            window,
 2554            cx,
 2555        );
 2556    });
 2557    // Same happens in the other direction.
 2558    cx.assert_editor_state("here is some textˇwith a space");
 2559
 2560    cx.set_state("here is some textˇ    with a space");
 2561    cx.update_editor(|editor, window, cx| {
 2562        editor.delete_to_next_word_end(
 2563            &DeleteToNextWordEnd {
 2564                ignore_newlines: false,
 2565                ignore_brackets: false,
 2566            },
 2567            window,
 2568            cx,
 2569        );
 2570    });
 2571    cx.assert_editor_state("here is some textˇwith a space");
 2572
 2573    cx.set_state("here is some textˇ    with a space");
 2574    cx.update_editor(|editor, window, cx| {
 2575        editor.delete_to_next_word_end(
 2576            &DeleteToNextWordEnd {
 2577                ignore_newlines: true,
 2578                ignore_brackets: false,
 2579            },
 2580            window,
 2581            cx,
 2582        );
 2583    });
 2584    cx.assert_editor_state("here is some textˇwith a space");
 2585    cx.update_editor(|editor, window, cx| {
 2586        editor.delete_to_previous_word_start(
 2587            &DeleteToPreviousWordStart {
 2588                ignore_newlines: true,
 2589                ignore_brackets: false,
 2590            },
 2591            window,
 2592            cx,
 2593        );
 2594    });
 2595    cx.assert_editor_state("here is some ˇwith a space");
 2596    cx.update_editor(|editor, window, cx| {
 2597        editor.delete_to_previous_word_start(
 2598            &DeleteToPreviousWordStart {
 2599                ignore_newlines: true,
 2600                ignore_brackets: false,
 2601            },
 2602            window,
 2603            cx,
 2604        );
 2605    });
 2606    // Single whitespaces are removed with the word behind them.
 2607    cx.assert_editor_state("here is ˇwith a space");
 2608    cx.update_editor(|editor, window, cx| {
 2609        editor.delete_to_previous_word_start(
 2610            &DeleteToPreviousWordStart {
 2611                ignore_newlines: true,
 2612                ignore_brackets: false,
 2613            },
 2614            window,
 2615            cx,
 2616        );
 2617    });
 2618    cx.assert_editor_state("here ˇwith a space");
 2619    cx.update_editor(|editor, window, cx| {
 2620        editor.delete_to_previous_word_start(
 2621            &DeleteToPreviousWordStart {
 2622                ignore_newlines: true,
 2623                ignore_brackets: false,
 2624            },
 2625            window,
 2626            cx,
 2627        );
 2628    });
 2629    cx.assert_editor_state("ˇwith a space");
 2630    cx.update_editor(|editor, window, cx| {
 2631        editor.delete_to_previous_word_start(
 2632            &DeleteToPreviousWordStart {
 2633                ignore_newlines: true,
 2634                ignore_brackets: false,
 2635            },
 2636            window,
 2637            cx,
 2638        );
 2639    });
 2640    cx.assert_editor_state("ˇwith a space");
 2641    cx.update_editor(|editor, window, cx| {
 2642        editor.delete_to_next_word_end(
 2643            &DeleteToNextWordEnd {
 2644                ignore_newlines: true,
 2645                ignore_brackets: false,
 2646            },
 2647            window,
 2648            cx,
 2649        );
 2650    });
 2651    // Same happens in the other direction.
 2652    cx.assert_editor_state("ˇ a space");
 2653    cx.update_editor(|editor, window, cx| {
 2654        editor.delete_to_next_word_end(
 2655            &DeleteToNextWordEnd {
 2656                ignore_newlines: true,
 2657                ignore_brackets: false,
 2658            },
 2659            window,
 2660            cx,
 2661        );
 2662    });
 2663    cx.assert_editor_state("ˇ space");
 2664    cx.update_editor(|editor, window, cx| {
 2665        editor.delete_to_next_word_end(
 2666            &DeleteToNextWordEnd {
 2667                ignore_newlines: true,
 2668                ignore_brackets: false,
 2669            },
 2670            window,
 2671            cx,
 2672        );
 2673    });
 2674    cx.assert_editor_state("ˇ");
 2675    cx.update_editor(|editor, window, cx| {
 2676        editor.delete_to_next_word_end(
 2677            &DeleteToNextWordEnd {
 2678                ignore_newlines: true,
 2679                ignore_brackets: false,
 2680            },
 2681            window,
 2682            cx,
 2683        );
 2684    });
 2685    cx.assert_editor_state("ˇ");
 2686    cx.update_editor(|editor, window, cx| {
 2687        editor.delete_to_previous_word_start(
 2688            &DeleteToPreviousWordStart {
 2689                ignore_newlines: true,
 2690                ignore_brackets: false,
 2691            },
 2692            window,
 2693            cx,
 2694        );
 2695    });
 2696    cx.assert_editor_state("ˇ");
 2697}
 2698
 2699#[gpui::test]
 2700async fn test_delete_to_bracket(cx: &mut TestAppContext) {
 2701    init_test(cx, |_| {});
 2702
 2703    let language = Arc::new(
 2704        Language::new(
 2705            LanguageConfig {
 2706                brackets: BracketPairConfig {
 2707                    pairs: vec![
 2708                        BracketPair {
 2709                            start: "\"".to_string(),
 2710                            end: "\"".to_string(),
 2711                            close: true,
 2712                            surround: true,
 2713                            newline: false,
 2714                        },
 2715                        BracketPair {
 2716                            start: "(".to_string(),
 2717                            end: ")".to_string(),
 2718                            close: true,
 2719                            surround: true,
 2720                            newline: true,
 2721                        },
 2722                    ],
 2723                    ..BracketPairConfig::default()
 2724                },
 2725                ..LanguageConfig::default()
 2726            },
 2727            Some(tree_sitter_rust::LANGUAGE.into()),
 2728        )
 2729        .with_brackets_query(
 2730            r#"
 2731                ("(" @open ")" @close)
 2732                ("\"" @open "\"" @close)
 2733            "#,
 2734        )
 2735        .unwrap(),
 2736    );
 2737
 2738    let mut cx = EditorTestContext::new(cx).await;
 2739    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2740
 2741    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 2742    cx.update_editor(|editor, window, cx| {
 2743        editor.delete_to_previous_word_start(
 2744            &DeleteToPreviousWordStart {
 2745                ignore_newlines: true,
 2746                ignore_brackets: false,
 2747            },
 2748            window,
 2749            cx,
 2750        );
 2751    });
 2752    // Deletion stops before brackets if asked to not ignore them.
 2753    cx.assert_editor_state(r#"macro!("ˇCOMMENT");"#);
 2754    cx.update_editor(|editor, window, cx| {
 2755        editor.delete_to_previous_word_start(
 2756            &DeleteToPreviousWordStart {
 2757                ignore_newlines: true,
 2758                ignore_brackets: false,
 2759            },
 2760            window,
 2761            cx,
 2762        );
 2763    });
 2764    // Deletion has to remove a single bracket and then stop again.
 2765    cx.assert_editor_state(r#"macro!(ˇCOMMENT");"#);
 2766
 2767    cx.update_editor(|editor, window, cx| {
 2768        editor.delete_to_previous_word_start(
 2769            &DeleteToPreviousWordStart {
 2770                ignore_newlines: true,
 2771                ignore_brackets: false,
 2772            },
 2773            window,
 2774            cx,
 2775        );
 2776    });
 2777    cx.assert_editor_state(r#"macro!ˇCOMMENT");"#);
 2778
 2779    cx.update_editor(|editor, window, cx| {
 2780        editor.delete_to_previous_word_start(
 2781            &DeleteToPreviousWordStart {
 2782                ignore_newlines: true,
 2783                ignore_brackets: false,
 2784            },
 2785            window,
 2786            cx,
 2787        );
 2788    });
 2789    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2790
 2791    cx.update_editor(|editor, window, cx| {
 2792        editor.delete_to_previous_word_start(
 2793            &DeleteToPreviousWordStart {
 2794                ignore_newlines: true,
 2795                ignore_brackets: false,
 2796            },
 2797            window,
 2798            cx,
 2799        );
 2800    });
 2801    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2802
 2803    cx.update_editor(|editor, window, cx| {
 2804        editor.delete_to_next_word_end(
 2805            &DeleteToNextWordEnd {
 2806                ignore_newlines: true,
 2807                ignore_brackets: false,
 2808            },
 2809            window,
 2810            cx,
 2811        );
 2812    });
 2813    // Brackets on the right are not paired anymore, hence deletion does not stop at them
 2814    cx.assert_editor_state(r#"ˇ");"#);
 2815
 2816    cx.update_editor(|editor, window, cx| {
 2817        editor.delete_to_next_word_end(
 2818            &DeleteToNextWordEnd {
 2819                ignore_newlines: true,
 2820                ignore_brackets: false,
 2821            },
 2822            window,
 2823            cx,
 2824        );
 2825    });
 2826    cx.assert_editor_state(r#"ˇ"#);
 2827
 2828    cx.update_editor(|editor, window, cx| {
 2829        editor.delete_to_next_word_end(
 2830            &DeleteToNextWordEnd {
 2831                ignore_newlines: true,
 2832                ignore_brackets: false,
 2833            },
 2834            window,
 2835            cx,
 2836        );
 2837    });
 2838    cx.assert_editor_state(r#"ˇ"#);
 2839
 2840    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 2841    cx.update_editor(|editor, window, cx| {
 2842        editor.delete_to_previous_word_start(
 2843            &DeleteToPreviousWordStart {
 2844                ignore_newlines: true,
 2845                ignore_brackets: true,
 2846            },
 2847            window,
 2848            cx,
 2849        );
 2850    });
 2851    cx.assert_editor_state(r#"macroˇCOMMENT");"#);
 2852}
 2853
 2854#[gpui::test]
 2855fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 2856    init_test(cx, |_| {});
 2857
 2858    let editor = cx.add_window(|window, cx| {
 2859        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 2860        build_editor(buffer, window, cx)
 2861    });
 2862    let del_to_prev_word_start = DeleteToPreviousWordStart {
 2863        ignore_newlines: false,
 2864        ignore_brackets: false,
 2865    };
 2866    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 2867        ignore_newlines: true,
 2868        ignore_brackets: false,
 2869    };
 2870
 2871    _ = editor.update(cx, |editor, window, cx| {
 2872        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2873            s.select_display_ranges([
 2874                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 2875            ])
 2876        });
 2877        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2878        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 2879        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2880        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
 2881        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2882        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
 2883        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2884        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
 2885        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2886        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 2887        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2888        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2889    });
 2890}
 2891
 2892#[gpui::test]
 2893fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 2894    init_test(cx, |_| {});
 2895
 2896    let editor = cx.add_window(|window, cx| {
 2897        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 2898        build_editor(buffer, window, cx)
 2899    });
 2900    let del_to_next_word_end = DeleteToNextWordEnd {
 2901        ignore_newlines: false,
 2902        ignore_brackets: false,
 2903    };
 2904    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 2905        ignore_newlines: true,
 2906        ignore_brackets: false,
 2907    };
 2908
 2909    _ = editor.update(cx, |editor, window, cx| {
 2910        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2911            s.select_display_ranges([
 2912                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 2913            ])
 2914        });
 2915        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2916        assert_eq!(
 2917            editor.buffer.read(cx).read(cx).text(),
 2918            "one\n   two\nthree\n   four"
 2919        );
 2920        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2921        assert_eq!(
 2922            editor.buffer.read(cx).read(cx).text(),
 2923            "\n   two\nthree\n   four"
 2924        );
 2925        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2926        assert_eq!(
 2927            editor.buffer.read(cx).read(cx).text(),
 2928            "two\nthree\n   four"
 2929        );
 2930        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2931        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 2932        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2933        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   four");
 2934        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2935        assert_eq!(editor.buffer.read(cx).read(cx).text(), "four");
 2936        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2937        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2938    });
 2939}
 2940
 2941#[gpui::test]
 2942fn test_newline(cx: &mut TestAppContext) {
 2943    init_test(cx, |_| {});
 2944
 2945    let editor = cx.add_window(|window, cx| {
 2946        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 2947        build_editor(buffer, window, cx)
 2948    });
 2949
 2950    _ = editor.update(cx, |editor, window, cx| {
 2951        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2952            s.select_display_ranges([
 2953                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2954                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2955                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 2956            ])
 2957        });
 2958
 2959        editor.newline(&Newline, window, cx);
 2960        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 2961    });
 2962}
 2963
 2964#[gpui::test]
 2965fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 2966    init_test(cx, |_| {});
 2967
 2968    let editor = cx.add_window(|window, cx| {
 2969        let buffer = MultiBuffer::build_simple(
 2970            "
 2971                a
 2972                b(
 2973                    X
 2974                )
 2975                c(
 2976                    X
 2977                )
 2978            "
 2979            .unindent()
 2980            .as_str(),
 2981            cx,
 2982        );
 2983        let mut editor = build_editor(buffer, window, cx);
 2984        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2985            s.select_ranges([
 2986                Point::new(2, 4)..Point::new(2, 5),
 2987                Point::new(5, 4)..Point::new(5, 5),
 2988            ])
 2989        });
 2990        editor
 2991    });
 2992
 2993    _ = editor.update(cx, |editor, window, cx| {
 2994        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 2995        editor.buffer.update(cx, |buffer, cx| {
 2996            buffer.edit(
 2997                [
 2998                    (Point::new(1, 2)..Point::new(3, 0), ""),
 2999                    (Point::new(4, 2)..Point::new(6, 0), ""),
 3000                ],
 3001                None,
 3002                cx,
 3003            );
 3004            assert_eq!(
 3005                buffer.read(cx).text(),
 3006                "
 3007                    a
 3008                    b()
 3009                    c()
 3010                "
 3011                .unindent()
 3012            );
 3013        });
 3014        assert_eq!(
 3015            editor.selections.ranges(cx),
 3016            &[
 3017                Point::new(1, 2)..Point::new(1, 2),
 3018                Point::new(2, 2)..Point::new(2, 2),
 3019            ],
 3020        );
 3021
 3022        editor.newline(&Newline, window, cx);
 3023        assert_eq!(
 3024            editor.text(cx),
 3025            "
 3026                a
 3027                b(
 3028                )
 3029                c(
 3030                )
 3031            "
 3032            .unindent()
 3033        );
 3034
 3035        // The selections are moved after the inserted newlines
 3036        assert_eq!(
 3037            editor.selections.ranges(cx),
 3038            &[
 3039                Point::new(2, 0)..Point::new(2, 0),
 3040                Point::new(4, 0)..Point::new(4, 0),
 3041            ],
 3042        );
 3043    });
 3044}
 3045
 3046#[gpui::test]
 3047async fn test_newline_above(cx: &mut TestAppContext) {
 3048    init_test(cx, |settings| {
 3049        settings.defaults.tab_size = NonZeroU32::new(4)
 3050    });
 3051
 3052    let language = Arc::new(
 3053        Language::new(
 3054            LanguageConfig::default(),
 3055            Some(tree_sitter_rust::LANGUAGE.into()),
 3056        )
 3057        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3058        .unwrap(),
 3059    );
 3060
 3061    let mut cx = EditorTestContext::new(cx).await;
 3062    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3063    cx.set_state(indoc! {"
 3064        const a: ˇA = (
 3065 3066                «const_functionˇ»(ˇ),
 3067                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3068 3069        ˇ);ˇ
 3070    "});
 3071
 3072    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 3073    cx.assert_editor_state(indoc! {"
 3074        ˇ
 3075        const a: A = (
 3076            ˇ
 3077            (
 3078                ˇ
 3079                ˇ
 3080                const_function(),
 3081                ˇ
 3082                ˇ
 3083                ˇ
 3084                ˇ
 3085                something_else,
 3086                ˇ
 3087            )
 3088            ˇ
 3089            ˇ
 3090        );
 3091    "});
 3092}
 3093
 3094#[gpui::test]
 3095async fn test_newline_below(cx: &mut TestAppContext) {
 3096    init_test(cx, |settings| {
 3097        settings.defaults.tab_size = NonZeroU32::new(4)
 3098    });
 3099
 3100    let language = Arc::new(
 3101        Language::new(
 3102            LanguageConfig::default(),
 3103            Some(tree_sitter_rust::LANGUAGE.into()),
 3104        )
 3105        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3106        .unwrap(),
 3107    );
 3108
 3109    let mut cx = EditorTestContext::new(cx).await;
 3110    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3111    cx.set_state(indoc! {"
 3112        const a: ˇA = (
 3113 3114                «const_functionˇ»(ˇ),
 3115                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3116 3117        ˇ);ˇ
 3118    "});
 3119
 3120    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 3121    cx.assert_editor_state(indoc! {"
 3122        const a: A = (
 3123            ˇ
 3124            (
 3125                ˇ
 3126                const_function(),
 3127                ˇ
 3128                ˇ
 3129                something_else,
 3130                ˇ
 3131                ˇ
 3132                ˇ
 3133                ˇ
 3134            )
 3135            ˇ
 3136        );
 3137        ˇ
 3138        ˇ
 3139    "});
 3140}
 3141
 3142#[gpui::test]
 3143async fn test_newline_comments(cx: &mut TestAppContext) {
 3144    init_test(cx, |settings| {
 3145        settings.defaults.tab_size = NonZeroU32::new(4)
 3146    });
 3147
 3148    let language = Arc::new(Language::new(
 3149        LanguageConfig {
 3150            line_comments: vec!["// ".into()],
 3151            ..LanguageConfig::default()
 3152        },
 3153        None,
 3154    ));
 3155    {
 3156        let mut cx = EditorTestContext::new(cx).await;
 3157        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3158        cx.set_state(indoc! {"
 3159        // Fooˇ
 3160    "});
 3161
 3162        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3163        cx.assert_editor_state(indoc! {"
 3164        // Foo
 3165        // ˇ
 3166    "});
 3167        // Ensure that we add comment prefix when existing line contains space
 3168        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3169        cx.assert_editor_state(
 3170            indoc! {"
 3171        // Foo
 3172        //s
 3173        // ˇ
 3174    "}
 3175            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3176            .as_str(),
 3177        );
 3178        // Ensure that we add comment prefix when existing line does not contain space
 3179        cx.set_state(indoc! {"
 3180        // Foo
 3181        //ˇ
 3182    "});
 3183        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3184        cx.assert_editor_state(indoc! {"
 3185        // Foo
 3186        //
 3187        // ˇ
 3188    "});
 3189        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 3190        cx.set_state(indoc! {"
 3191        ˇ// Foo
 3192    "});
 3193        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3194        cx.assert_editor_state(indoc! {"
 3195
 3196        ˇ// Foo
 3197    "});
 3198    }
 3199    // Ensure that comment continuations can be disabled.
 3200    update_test_language_settings(cx, |settings| {
 3201        settings.defaults.extend_comment_on_newline = Some(false);
 3202    });
 3203    let mut cx = EditorTestContext::new(cx).await;
 3204    cx.set_state(indoc! {"
 3205        // Fooˇ
 3206    "});
 3207    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3208    cx.assert_editor_state(indoc! {"
 3209        // Foo
 3210        ˇ
 3211    "});
 3212}
 3213
 3214#[gpui::test]
 3215async fn test_newline_comments_with_multiple_delimiters(cx: &mut TestAppContext) {
 3216    init_test(cx, |settings| {
 3217        settings.defaults.tab_size = NonZeroU32::new(4)
 3218    });
 3219
 3220    let language = Arc::new(Language::new(
 3221        LanguageConfig {
 3222            line_comments: vec!["// ".into(), "/// ".into()],
 3223            ..LanguageConfig::default()
 3224        },
 3225        None,
 3226    ));
 3227    {
 3228        let mut cx = EditorTestContext::new(cx).await;
 3229        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3230        cx.set_state(indoc! {"
 3231        //ˇ
 3232    "});
 3233        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3234        cx.assert_editor_state(indoc! {"
 3235        //
 3236        // ˇ
 3237    "});
 3238
 3239        cx.set_state(indoc! {"
 3240        ///ˇ
 3241    "});
 3242        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3243        cx.assert_editor_state(indoc! {"
 3244        ///
 3245        /// ˇ
 3246    "});
 3247    }
 3248}
 3249
 3250#[gpui::test]
 3251async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 3252    init_test(cx, |settings| {
 3253        settings.defaults.tab_size = NonZeroU32::new(4)
 3254    });
 3255
 3256    let language = Arc::new(
 3257        Language::new(
 3258            LanguageConfig {
 3259                documentation_comment: Some(language::BlockCommentConfig {
 3260                    start: "/**".into(),
 3261                    end: "*/".into(),
 3262                    prefix: "* ".into(),
 3263                    tab_size: 1,
 3264                }),
 3265
 3266                ..LanguageConfig::default()
 3267            },
 3268            Some(tree_sitter_rust::LANGUAGE.into()),
 3269        )
 3270        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 3271        .unwrap(),
 3272    );
 3273
 3274    {
 3275        let mut cx = EditorTestContext::new(cx).await;
 3276        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3277        cx.set_state(indoc! {"
 3278        /**ˇ
 3279    "});
 3280
 3281        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3282        cx.assert_editor_state(indoc! {"
 3283        /**
 3284         * ˇ
 3285    "});
 3286        // Ensure that if cursor is before the comment start,
 3287        // we do not actually insert a comment prefix.
 3288        cx.set_state(indoc! {"
 3289        ˇ/**
 3290    "});
 3291        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3292        cx.assert_editor_state(indoc! {"
 3293
 3294        ˇ/**
 3295    "});
 3296        // Ensure that if cursor is between it doesn't add comment prefix.
 3297        cx.set_state(indoc! {"
 3298        /*ˇ*
 3299    "});
 3300        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3301        cx.assert_editor_state(indoc! {"
 3302        /*
 3303        ˇ*
 3304    "});
 3305        // Ensure that if suffix exists on same line after cursor it adds new line.
 3306        cx.set_state(indoc! {"
 3307        /**ˇ*/
 3308    "});
 3309        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3310        cx.assert_editor_state(indoc! {"
 3311        /**
 3312         * ˇ
 3313         */
 3314    "});
 3315        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3316        cx.set_state(indoc! {"
 3317        /**ˇ */
 3318    "});
 3319        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3320        cx.assert_editor_state(indoc! {"
 3321        /**
 3322         * ˇ
 3323         */
 3324    "});
 3325        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3326        cx.set_state(indoc! {"
 3327        /** ˇ*/
 3328    "});
 3329        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3330        cx.assert_editor_state(
 3331            indoc! {"
 3332        /**s
 3333         * ˇ
 3334         */
 3335    "}
 3336            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3337            .as_str(),
 3338        );
 3339        // Ensure that delimiter space is preserved when newline on already
 3340        // spaced delimiter.
 3341        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3342        cx.assert_editor_state(
 3343            indoc! {"
 3344        /**s
 3345         *s
 3346         * ˇ
 3347         */
 3348    "}
 3349            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3350            .as_str(),
 3351        );
 3352        // Ensure that delimiter space is preserved when space is not
 3353        // on existing delimiter.
 3354        cx.set_state(indoc! {"
 3355        /**
 3356 3357         */
 3358    "});
 3359        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3360        cx.assert_editor_state(indoc! {"
 3361        /**
 3362         *
 3363         * ˇ
 3364         */
 3365    "});
 3366        // Ensure that if suffix exists on same line after cursor it
 3367        // doesn't add extra new line if prefix is not on same line.
 3368        cx.set_state(indoc! {"
 3369        /**
 3370        ˇ*/
 3371    "});
 3372        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3373        cx.assert_editor_state(indoc! {"
 3374        /**
 3375
 3376        ˇ*/
 3377    "});
 3378        // Ensure that it detects suffix after existing prefix.
 3379        cx.set_state(indoc! {"
 3380        /**ˇ/
 3381    "});
 3382        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3383        cx.assert_editor_state(indoc! {"
 3384        /**
 3385        ˇ/
 3386    "});
 3387        // Ensure that if suffix exists on same line before
 3388        // cursor it does not add comment prefix.
 3389        cx.set_state(indoc! {"
 3390        /** */ˇ
 3391    "});
 3392        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3393        cx.assert_editor_state(indoc! {"
 3394        /** */
 3395        ˇ
 3396    "});
 3397        // Ensure that if suffix exists on same line before
 3398        // cursor it does not add comment prefix.
 3399        cx.set_state(indoc! {"
 3400        /**
 3401         *
 3402         */ˇ
 3403    "});
 3404        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3405        cx.assert_editor_state(indoc! {"
 3406        /**
 3407         *
 3408         */
 3409         ˇ
 3410    "});
 3411
 3412        // Ensure that inline comment followed by code
 3413        // doesn't add comment prefix on newline
 3414        cx.set_state(indoc! {"
 3415        /** */ textˇ
 3416    "});
 3417        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3418        cx.assert_editor_state(indoc! {"
 3419        /** */ text
 3420        ˇ
 3421    "});
 3422
 3423        // Ensure that text after comment end tag
 3424        // doesn't add comment prefix on newline
 3425        cx.set_state(indoc! {"
 3426        /**
 3427         *
 3428         */ˇtext
 3429    "});
 3430        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3431        cx.assert_editor_state(indoc! {"
 3432        /**
 3433         *
 3434         */
 3435         ˇtext
 3436    "});
 3437
 3438        // Ensure if not comment block it doesn't
 3439        // add comment prefix on newline
 3440        cx.set_state(indoc! {"
 3441        * textˇ
 3442    "});
 3443        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3444        cx.assert_editor_state(indoc! {"
 3445        * text
 3446        ˇ
 3447    "});
 3448    }
 3449    // Ensure that comment continuations can be disabled.
 3450    update_test_language_settings(cx, |settings| {
 3451        settings.defaults.extend_comment_on_newline = Some(false);
 3452    });
 3453    let mut cx = EditorTestContext::new(cx).await;
 3454    cx.set_state(indoc! {"
 3455        /**ˇ
 3456    "});
 3457    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3458    cx.assert_editor_state(indoc! {"
 3459        /**
 3460        ˇ
 3461    "});
 3462}
 3463
 3464#[gpui::test]
 3465async fn test_newline_comments_with_block_comment(cx: &mut TestAppContext) {
 3466    init_test(cx, |settings| {
 3467        settings.defaults.tab_size = NonZeroU32::new(4)
 3468    });
 3469
 3470    let lua_language = Arc::new(Language::new(
 3471        LanguageConfig {
 3472            line_comments: vec!["--".into()],
 3473            block_comment: Some(language::BlockCommentConfig {
 3474                start: "--[[".into(),
 3475                prefix: "".into(),
 3476                end: "]]".into(),
 3477                tab_size: 0,
 3478            }),
 3479            ..LanguageConfig::default()
 3480        },
 3481        None,
 3482    ));
 3483
 3484    let mut cx = EditorTestContext::new(cx).await;
 3485    cx.update_buffer(|buffer, cx| buffer.set_language(Some(lua_language), cx));
 3486
 3487    // Line with line comment should extend
 3488    cx.set_state(indoc! {"
 3489        --ˇ
 3490    "});
 3491    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3492    cx.assert_editor_state(indoc! {"
 3493        --
 3494        --ˇ
 3495    "});
 3496
 3497    // Line with block comment that matches line comment should not extend
 3498    cx.set_state(indoc! {"
 3499        --[[ˇ
 3500    "});
 3501    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3502    cx.assert_editor_state(indoc! {"
 3503        --[[
 3504        ˇ
 3505    "});
 3506}
 3507
 3508#[gpui::test]
 3509fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 3510    init_test(cx, |_| {});
 3511
 3512    let editor = cx.add_window(|window, cx| {
 3513        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 3514        let mut editor = build_editor(buffer, window, cx);
 3515        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3516            s.select_ranges([3..4, 11..12, 19..20])
 3517        });
 3518        editor
 3519    });
 3520
 3521    _ = editor.update(cx, |editor, window, cx| {
 3522        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3523        editor.buffer.update(cx, |buffer, cx| {
 3524            buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
 3525            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 3526        });
 3527        assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
 3528
 3529        editor.insert("Z", window, cx);
 3530        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 3531
 3532        // The selections are moved after the inserted characters
 3533        assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
 3534    });
 3535}
 3536
 3537#[gpui::test]
 3538async fn test_tab(cx: &mut TestAppContext) {
 3539    init_test(cx, |settings| {
 3540        settings.defaults.tab_size = NonZeroU32::new(3)
 3541    });
 3542
 3543    let mut cx = EditorTestContext::new(cx).await;
 3544    cx.set_state(indoc! {"
 3545        ˇabˇc
 3546        ˇ🏀ˇ🏀ˇefg
 3547 3548    "});
 3549    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3550    cx.assert_editor_state(indoc! {"
 3551           ˇab ˇc
 3552           ˇ🏀  ˇ🏀  ˇefg
 3553        d  ˇ
 3554    "});
 3555
 3556    cx.set_state(indoc! {"
 3557        a
 3558        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3559    "});
 3560    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3561    cx.assert_editor_state(indoc! {"
 3562        a
 3563           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3564    "});
 3565}
 3566
 3567#[gpui::test]
 3568async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3569    init_test(cx, |_| {});
 3570
 3571    let mut cx = EditorTestContext::new(cx).await;
 3572    let language = Arc::new(
 3573        Language::new(
 3574            LanguageConfig::default(),
 3575            Some(tree_sitter_rust::LANGUAGE.into()),
 3576        )
 3577        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3578        .unwrap(),
 3579    );
 3580    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3581
 3582    // test when all cursors are not at suggested indent
 3583    // then simply move to their suggested indent location
 3584    cx.set_state(indoc! {"
 3585        const a: B = (
 3586            c(
 3587        ˇ
 3588        ˇ    )
 3589        );
 3590    "});
 3591    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3592    cx.assert_editor_state(indoc! {"
 3593        const a: B = (
 3594            c(
 3595                ˇ
 3596            ˇ)
 3597        );
 3598    "});
 3599
 3600    // test cursor already at suggested indent not moving when
 3601    // other cursors are yet to reach their suggested indents
 3602    cx.set_state(indoc! {"
 3603        ˇ
 3604        const a: B = (
 3605            c(
 3606                d(
 3607        ˇ
 3608                )
 3609        ˇ
 3610        ˇ    )
 3611        );
 3612    "});
 3613    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3614    cx.assert_editor_state(indoc! {"
 3615        ˇ
 3616        const a: B = (
 3617            c(
 3618                d(
 3619                    ˇ
 3620                )
 3621                ˇ
 3622            ˇ)
 3623        );
 3624    "});
 3625    // test when all cursors are at suggested indent then tab is inserted
 3626    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3627    cx.assert_editor_state(indoc! {"
 3628            ˇ
 3629        const a: B = (
 3630            c(
 3631                d(
 3632                        ˇ
 3633                )
 3634                    ˇ
 3635                ˇ)
 3636        );
 3637    "});
 3638
 3639    // test when current indent is less than suggested indent,
 3640    // we adjust line to match suggested indent and move cursor to it
 3641    //
 3642    // when no other cursor is at word boundary, all of them should move
 3643    cx.set_state(indoc! {"
 3644        const a: B = (
 3645            c(
 3646                d(
 3647        ˇ
 3648        ˇ   )
 3649        ˇ   )
 3650        );
 3651    "});
 3652    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3653    cx.assert_editor_state(indoc! {"
 3654        const a: B = (
 3655            c(
 3656                d(
 3657                    ˇ
 3658                ˇ)
 3659            ˇ)
 3660        );
 3661    "});
 3662
 3663    // test when current indent is less than suggested indent,
 3664    // we adjust line to match suggested indent and move cursor to it
 3665    //
 3666    // when some other cursor is at word boundary, it should not move
 3667    cx.set_state(indoc! {"
 3668        const a: B = (
 3669            c(
 3670                d(
 3671        ˇ
 3672        ˇ   )
 3673           ˇ)
 3674        );
 3675    "});
 3676    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3677    cx.assert_editor_state(indoc! {"
 3678        const a: B = (
 3679            c(
 3680                d(
 3681                    ˇ
 3682                ˇ)
 3683            ˇ)
 3684        );
 3685    "});
 3686
 3687    // test when current indent is more than suggested indent,
 3688    // we just move cursor to current indent instead of suggested indent
 3689    //
 3690    // when no other cursor is at word boundary, all of them should move
 3691    cx.set_state(indoc! {"
 3692        const a: B = (
 3693            c(
 3694                d(
 3695        ˇ
 3696        ˇ                )
 3697        ˇ   )
 3698        );
 3699    "});
 3700    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3701    cx.assert_editor_state(indoc! {"
 3702        const a: B = (
 3703            c(
 3704                d(
 3705                    ˇ
 3706                        ˇ)
 3707            ˇ)
 3708        );
 3709    "});
 3710    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3711    cx.assert_editor_state(indoc! {"
 3712        const a: B = (
 3713            c(
 3714                d(
 3715                        ˇ
 3716                            ˇ)
 3717                ˇ)
 3718        );
 3719    "});
 3720
 3721    // test when current indent is more than suggested indent,
 3722    // we just move cursor to current indent instead of suggested indent
 3723    //
 3724    // when some other cursor is at word boundary, it doesn't move
 3725    cx.set_state(indoc! {"
 3726        const a: B = (
 3727            c(
 3728                d(
 3729        ˇ
 3730        ˇ                )
 3731            ˇ)
 3732        );
 3733    "});
 3734    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3735    cx.assert_editor_state(indoc! {"
 3736        const a: B = (
 3737            c(
 3738                d(
 3739                    ˇ
 3740                        ˇ)
 3741            ˇ)
 3742        );
 3743    "});
 3744
 3745    // handle auto-indent when there are multiple cursors on the same line
 3746    cx.set_state(indoc! {"
 3747        const a: B = (
 3748            c(
 3749        ˇ    ˇ
 3750        ˇ    )
 3751        );
 3752    "});
 3753    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3754    cx.assert_editor_state(indoc! {"
 3755        const a: B = (
 3756            c(
 3757                ˇ
 3758            ˇ)
 3759        );
 3760    "});
 3761}
 3762
 3763#[gpui::test]
 3764async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 3765    init_test(cx, |settings| {
 3766        settings.defaults.tab_size = NonZeroU32::new(3)
 3767    });
 3768
 3769    let mut cx = EditorTestContext::new(cx).await;
 3770    cx.set_state(indoc! {"
 3771         ˇ
 3772        \t ˇ
 3773        \t  ˇ
 3774        \t   ˇ
 3775         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 3776    "});
 3777
 3778    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3779    cx.assert_editor_state(indoc! {"
 3780           ˇ
 3781        \t   ˇ
 3782        \t   ˇ
 3783        \t      ˇ
 3784         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 3785    "});
 3786}
 3787
 3788#[gpui::test]
 3789async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 3790    init_test(cx, |settings| {
 3791        settings.defaults.tab_size = NonZeroU32::new(4)
 3792    });
 3793
 3794    let language = Arc::new(
 3795        Language::new(
 3796            LanguageConfig::default(),
 3797            Some(tree_sitter_rust::LANGUAGE.into()),
 3798        )
 3799        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 3800        .unwrap(),
 3801    );
 3802
 3803    let mut cx = EditorTestContext::new(cx).await;
 3804    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3805    cx.set_state(indoc! {"
 3806        fn a() {
 3807            if b {
 3808        \t ˇc
 3809            }
 3810        }
 3811    "});
 3812
 3813    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3814    cx.assert_editor_state(indoc! {"
 3815        fn a() {
 3816            if b {
 3817                ˇc
 3818            }
 3819        }
 3820    "});
 3821}
 3822
 3823#[gpui::test]
 3824async fn test_indent_outdent(cx: &mut TestAppContext) {
 3825    init_test(cx, |settings| {
 3826        settings.defaults.tab_size = NonZeroU32::new(4);
 3827    });
 3828
 3829    let mut cx = EditorTestContext::new(cx).await;
 3830
 3831    cx.set_state(indoc! {"
 3832          «oneˇ» «twoˇ»
 3833        three
 3834         four
 3835    "});
 3836    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3837    cx.assert_editor_state(indoc! {"
 3838            «oneˇ» «twoˇ»
 3839        three
 3840         four
 3841    "});
 3842
 3843    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3844    cx.assert_editor_state(indoc! {"
 3845        «oneˇ» «twoˇ»
 3846        three
 3847         four
 3848    "});
 3849
 3850    // select across line ending
 3851    cx.set_state(indoc! {"
 3852        one two
 3853        t«hree
 3854        ˇ» four
 3855    "});
 3856    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3857    cx.assert_editor_state(indoc! {"
 3858        one two
 3859            t«hree
 3860        ˇ» four
 3861    "});
 3862
 3863    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3864    cx.assert_editor_state(indoc! {"
 3865        one two
 3866        t«hree
 3867        ˇ» four
 3868    "});
 3869
 3870    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3871    cx.set_state(indoc! {"
 3872        one two
 3873        ˇthree
 3874            four
 3875    "});
 3876    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3877    cx.assert_editor_state(indoc! {"
 3878        one two
 3879            ˇthree
 3880            four
 3881    "});
 3882
 3883    cx.set_state(indoc! {"
 3884        one two
 3885        ˇ    three
 3886            four
 3887    "});
 3888    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3889    cx.assert_editor_state(indoc! {"
 3890        one two
 3891        ˇthree
 3892            four
 3893    "});
 3894}
 3895
 3896#[gpui::test]
 3897async fn test_indent_yaml_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 3898    // This is a regression test for issue #33761
 3899    init_test(cx, |_| {});
 3900
 3901    let mut cx = EditorTestContext::new(cx).await;
 3902    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3903    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3904
 3905    cx.set_state(
 3906        r#"ˇ#     ingress:
 3907ˇ#         api:
 3908ˇ#             enabled: false
 3909ˇ#             pathType: Prefix
 3910ˇ#           console:
 3911ˇ#               enabled: false
 3912ˇ#               pathType: Prefix
 3913"#,
 3914    );
 3915
 3916    // Press tab to indent all lines
 3917    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3918
 3919    cx.assert_editor_state(
 3920        r#"    ˇ#     ingress:
 3921    ˇ#         api:
 3922    ˇ#             enabled: false
 3923    ˇ#             pathType: Prefix
 3924    ˇ#           console:
 3925    ˇ#               enabled: false
 3926    ˇ#               pathType: Prefix
 3927"#,
 3928    );
 3929}
 3930
 3931#[gpui::test]
 3932async fn test_indent_yaml_non_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 3933    // This is a test to make sure our fix for issue #33761 didn't break anything
 3934    init_test(cx, |_| {});
 3935
 3936    let mut cx = EditorTestContext::new(cx).await;
 3937    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3938    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3939
 3940    cx.set_state(
 3941        r#"ˇingress:
 3942ˇ  api:
 3943ˇ    enabled: false
 3944ˇ    pathType: Prefix
 3945"#,
 3946    );
 3947
 3948    // Press tab to indent all lines
 3949    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3950
 3951    cx.assert_editor_state(
 3952        r#"ˇingress:
 3953    ˇapi:
 3954        ˇenabled: false
 3955        ˇpathType: Prefix
 3956"#,
 3957    );
 3958}
 3959
 3960#[gpui::test]
 3961async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 3962    init_test(cx, |settings| {
 3963        settings.defaults.hard_tabs = Some(true);
 3964    });
 3965
 3966    let mut cx = EditorTestContext::new(cx).await;
 3967
 3968    // select two ranges on one line
 3969    cx.set_state(indoc! {"
 3970        «oneˇ» «twoˇ»
 3971        three
 3972        four
 3973    "});
 3974    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3975    cx.assert_editor_state(indoc! {"
 3976        \t«oneˇ» «twoˇ»
 3977        three
 3978        four
 3979    "});
 3980    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3981    cx.assert_editor_state(indoc! {"
 3982        \t\t«oneˇ» «twoˇ»
 3983        three
 3984        four
 3985    "});
 3986    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3987    cx.assert_editor_state(indoc! {"
 3988        \t«oneˇ» «twoˇ»
 3989        three
 3990        four
 3991    "});
 3992    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3993    cx.assert_editor_state(indoc! {"
 3994        «oneˇ» «twoˇ»
 3995        three
 3996        four
 3997    "});
 3998
 3999    // select across a line ending
 4000    cx.set_state(indoc! {"
 4001        one two
 4002        t«hree
 4003        ˇ»four
 4004    "});
 4005    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4006    cx.assert_editor_state(indoc! {"
 4007        one two
 4008        \tt«hree
 4009        ˇ»four
 4010    "});
 4011    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4012    cx.assert_editor_state(indoc! {"
 4013        one two
 4014        \t\tt«hree
 4015        ˇ»four
 4016    "});
 4017    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4018    cx.assert_editor_state(indoc! {"
 4019        one two
 4020        \tt«hree
 4021        ˇ»four
 4022    "});
 4023    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4024    cx.assert_editor_state(indoc! {"
 4025        one two
 4026        t«hree
 4027        ˇ»four
 4028    "});
 4029
 4030    // Ensure that indenting/outdenting works when the cursor is at column 0.
 4031    cx.set_state(indoc! {"
 4032        one two
 4033        ˇthree
 4034        four
 4035    "});
 4036    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4037    cx.assert_editor_state(indoc! {"
 4038        one two
 4039        ˇthree
 4040        four
 4041    "});
 4042    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4043    cx.assert_editor_state(indoc! {"
 4044        one two
 4045        \tˇthree
 4046        four
 4047    "});
 4048    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4049    cx.assert_editor_state(indoc! {"
 4050        one two
 4051        ˇthree
 4052        four
 4053    "});
 4054}
 4055
 4056#[gpui::test]
 4057fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 4058    init_test(cx, |settings| {
 4059        settings.languages.0.extend([
 4060            (
 4061                "TOML".into(),
 4062                LanguageSettingsContent {
 4063                    tab_size: NonZeroU32::new(2),
 4064                    ..Default::default()
 4065                },
 4066            ),
 4067            (
 4068                "Rust".into(),
 4069                LanguageSettingsContent {
 4070                    tab_size: NonZeroU32::new(4),
 4071                    ..Default::default()
 4072                },
 4073            ),
 4074        ]);
 4075    });
 4076
 4077    let toml_language = Arc::new(Language::new(
 4078        LanguageConfig {
 4079            name: "TOML".into(),
 4080            ..Default::default()
 4081        },
 4082        None,
 4083    ));
 4084    let rust_language = Arc::new(Language::new(
 4085        LanguageConfig {
 4086            name: "Rust".into(),
 4087            ..Default::default()
 4088        },
 4089        None,
 4090    ));
 4091
 4092    let toml_buffer =
 4093        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 4094    let rust_buffer =
 4095        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 4096    let multibuffer = cx.new(|cx| {
 4097        let mut multibuffer = MultiBuffer::new(ReadWrite);
 4098        multibuffer.push_excerpts(
 4099            toml_buffer.clone(),
 4100            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 4101            cx,
 4102        );
 4103        multibuffer.push_excerpts(
 4104            rust_buffer.clone(),
 4105            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 4106            cx,
 4107        );
 4108        multibuffer
 4109    });
 4110
 4111    cx.add_window(|window, cx| {
 4112        let mut editor = build_editor(multibuffer, window, cx);
 4113
 4114        assert_eq!(
 4115            editor.text(cx),
 4116            indoc! {"
 4117                a = 1
 4118                b = 2
 4119
 4120                const c: usize = 3;
 4121            "}
 4122        );
 4123
 4124        select_ranges(
 4125            &mut editor,
 4126            indoc! {"
 4127                «aˇ» = 1
 4128                b = 2
 4129
 4130                «const c:ˇ» usize = 3;
 4131            "},
 4132            window,
 4133            cx,
 4134        );
 4135
 4136        editor.tab(&Tab, window, cx);
 4137        assert_text_with_selections(
 4138            &mut editor,
 4139            indoc! {"
 4140                  «aˇ» = 1
 4141                b = 2
 4142
 4143                    «const c:ˇ» usize = 3;
 4144            "},
 4145            cx,
 4146        );
 4147        editor.backtab(&Backtab, window, cx);
 4148        assert_text_with_selections(
 4149            &mut editor,
 4150            indoc! {"
 4151                «aˇ» = 1
 4152                b = 2
 4153
 4154                «const c:ˇ» usize = 3;
 4155            "},
 4156            cx,
 4157        );
 4158
 4159        editor
 4160    });
 4161}
 4162
 4163#[gpui::test]
 4164async fn test_backspace(cx: &mut TestAppContext) {
 4165    init_test(cx, |_| {});
 4166
 4167    let mut cx = EditorTestContext::new(cx).await;
 4168
 4169    // Basic backspace
 4170    cx.set_state(indoc! {"
 4171        onˇe two three
 4172        fou«rˇ» five six
 4173        seven «ˇeight nine
 4174        »ten
 4175    "});
 4176    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4177    cx.assert_editor_state(indoc! {"
 4178        oˇe two three
 4179        fouˇ five six
 4180        seven ˇten
 4181    "});
 4182
 4183    // Test backspace inside and around indents
 4184    cx.set_state(indoc! {"
 4185        zero
 4186            ˇone
 4187                ˇtwo
 4188            ˇ ˇ ˇ  three
 4189        ˇ  ˇ  four
 4190    "});
 4191    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4192    cx.assert_editor_state(indoc! {"
 4193        zero
 4194        ˇone
 4195            ˇtwo
 4196        ˇ  threeˇ  four
 4197    "});
 4198}
 4199
 4200#[gpui::test]
 4201async fn test_delete(cx: &mut TestAppContext) {
 4202    init_test(cx, |_| {});
 4203
 4204    let mut cx = EditorTestContext::new(cx).await;
 4205    cx.set_state(indoc! {"
 4206        onˇe two three
 4207        fou«rˇ» five six
 4208        seven «ˇeight nine
 4209        »ten
 4210    "});
 4211    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 4212    cx.assert_editor_state(indoc! {"
 4213        onˇ two three
 4214        fouˇ five six
 4215        seven ˇten
 4216    "});
 4217}
 4218
 4219#[gpui::test]
 4220fn test_delete_line(cx: &mut TestAppContext) {
 4221    init_test(cx, |_| {});
 4222
 4223    let editor = cx.add_window(|window, cx| {
 4224        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4225        build_editor(buffer, window, cx)
 4226    });
 4227    _ = editor.update(cx, |editor, window, cx| {
 4228        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4229            s.select_display_ranges([
 4230                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4231                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4232                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4233            ])
 4234        });
 4235        editor.delete_line(&DeleteLine, window, cx);
 4236        assert_eq!(editor.display_text(cx), "ghi");
 4237        assert_eq!(
 4238            editor.selections.display_ranges(cx),
 4239            vec![
 4240                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 4241                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
 4242            ]
 4243        );
 4244    });
 4245
 4246    let editor = cx.add_window(|window, cx| {
 4247        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4248        build_editor(buffer, window, cx)
 4249    });
 4250    _ = editor.update(cx, |editor, window, cx| {
 4251        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4252            s.select_display_ranges([
 4253                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 4254            ])
 4255        });
 4256        editor.delete_line(&DeleteLine, window, cx);
 4257        assert_eq!(editor.display_text(cx), "ghi\n");
 4258        assert_eq!(
 4259            editor.selections.display_ranges(cx),
 4260            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 4261        );
 4262    });
 4263}
 4264
 4265#[gpui::test]
 4266fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 4267    init_test(cx, |_| {});
 4268
 4269    cx.add_window(|window, cx| {
 4270        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4271        let mut editor = build_editor(buffer.clone(), window, cx);
 4272        let buffer = buffer.read(cx).as_singleton().unwrap();
 4273
 4274        assert_eq!(
 4275            editor.selections.ranges::<Point>(cx),
 4276            &[Point::new(0, 0)..Point::new(0, 0)]
 4277        );
 4278
 4279        // When on single line, replace newline at end by space
 4280        editor.join_lines(&JoinLines, window, cx);
 4281        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4282        assert_eq!(
 4283            editor.selections.ranges::<Point>(cx),
 4284            &[Point::new(0, 3)..Point::new(0, 3)]
 4285        );
 4286
 4287        // When multiple lines are selected, remove newlines that are spanned by the selection
 4288        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4289            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 4290        });
 4291        editor.join_lines(&JoinLines, window, cx);
 4292        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 4293        assert_eq!(
 4294            editor.selections.ranges::<Point>(cx),
 4295            &[Point::new(0, 11)..Point::new(0, 11)]
 4296        );
 4297
 4298        // Undo should be transactional
 4299        editor.undo(&Undo, window, cx);
 4300        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4301        assert_eq!(
 4302            editor.selections.ranges::<Point>(cx),
 4303            &[Point::new(0, 5)..Point::new(2, 2)]
 4304        );
 4305
 4306        // When joining an empty line don't insert a space
 4307        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4308            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 4309        });
 4310        editor.join_lines(&JoinLines, window, cx);
 4311        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 4312        assert_eq!(
 4313            editor.selections.ranges::<Point>(cx),
 4314            [Point::new(2, 3)..Point::new(2, 3)]
 4315        );
 4316
 4317        // We can remove trailing newlines
 4318        editor.join_lines(&JoinLines, window, cx);
 4319        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4320        assert_eq!(
 4321            editor.selections.ranges::<Point>(cx),
 4322            [Point::new(2, 3)..Point::new(2, 3)]
 4323        );
 4324
 4325        // We don't blow up on the last line
 4326        editor.join_lines(&JoinLines, window, cx);
 4327        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4328        assert_eq!(
 4329            editor.selections.ranges::<Point>(cx),
 4330            [Point::new(2, 3)..Point::new(2, 3)]
 4331        );
 4332
 4333        // reset to test indentation
 4334        editor.buffer.update(cx, |buffer, cx| {
 4335            buffer.edit(
 4336                [
 4337                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 4338                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 4339                ],
 4340                None,
 4341                cx,
 4342            )
 4343        });
 4344
 4345        // We remove any leading spaces
 4346        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 4347        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4348            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 4349        });
 4350        editor.join_lines(&JoinLines, window, cx);
 4351        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 4352
 4353        // We don't insert a space for a line containing only spaces
 4354        editor.join_lines(&JoinLines, window, cx);
 4355        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 4356
 4357        // We ignore any leading tabs
 4358        editor.join_lines(&JoinLines, window, cx);
 4359        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 4360
 4361        editor
 4362    });
 4363}
 4364
 4365#[gpui::test]
 4366fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 4367    init_test(cx, |_| {});
 4368
 4369    cx.add_window(|window, cx| {
 4370        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4371        let mut editor = build_editor(buffer.clone(), window, cx);
 4372        let buffer = buffer.read(cx).as_singleton().unwrap();
 4373
 4374        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4375            s.select_ranges([
 4376                Point::new(0, 2)..Point::new(1, 1),
 4377                Point::new(1, 2)..Point::new(1, 2),
 4378                Point::new(3, 1)..Point::new(3, 2),
 4379            ])
 4380        });
 4381
 4382        editor.join_lines(&JoinLines, window, cx);
 4383        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 4384
 4385        assert_eq!(
 4386            editor.selections.ranges::<Point>(cx),
 4387            [
 4388                Point::new(0, 7)..Point::new(0, 7),
 4389                Point::new(1, 3)..Point::new(1, 3)
 4390            ]
 4391        );
 4392        editor
 4393    });
 4394}
 4395
 4396#[gpui::test]
 4397async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 4398    init_test(cx, |_| {});
 4399
 4400    let mut cx = EditorTestContext::new(cx).await;
 4401
 4402    let diff_base = r#"
 4403        Line 0
 4404        Line 1
 4405        Line 2
 4406        Line 3
 4407        "#
 4408    .unindent();
 4409
 4410    cx.set_state(
 4411        &r#"
 4412        ˇLine 0
 4413        Line 1
 4414        Line 2
 4415        Line 3
 4416        "#
 4417        .unindent(),
 4418    );
 4419
 4420    cx.set_head_text(&diff_base);
 4421    executor.run_until_parked();
 4422
 4423    // Join lines
 4424    cx.update_editor(|editor, window, cx| {
 4425        editor.join_lines(&JoinLines, window, cx);
 4426    });
 4427    executor.run_until_parked();
 4428
 4429    cx.assert_editor_state(
 4430        &r#"
 4431        Line 0ˇ Line 1
 4432        Line 2
 4433        Line 3
 4434        "#
 4435        .unindent(),
 4436    );
 4437    // Join again
 4438    cx.update_editor(|editor, window, cx| {
 4439        editor.join_lines(&JoinLines, window, cx);
 4440    });
 4441    executor.run_until_parked();
 4442
 4443    cx.assert_editor_state(
 4444        &r#"
 4445        Line 0 Line 1ˇ Line 2
 4446        Line 3
 4447        "#
 4448        .unindent(),
 4449    );
 4450}
 4451
 4452#[gpui::test]
 4453async fn test_custom_newlines_cause_no_false_positive_diffs(
 4454    executor: BackgroundExecutor,
 4455    cx: &mut TestAppContext,
 4456) {
 4457    init_test(cx, |_| {});
 4458    let mut cx = EditorTestContext::new(cx).await;
 4459    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 4460    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 4461    executor.run_until_parked();
 4462
 4463    cx.update_editor(|editor, window, cx| {
 4464        let snapshot = editor.snapshot(window, cx);
 4465        assert_eq!(
 4466            snapshot
 4467                .buffer_snapshot
 4468                .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
 4469                .collect::<Vec<_>>(),
 4470            Vec::new(),
 4471            "Should not have any diffs for files with custom newlines"
 4472        );
 4473    });
 4474}
 4475
 4476#[gpui::test]
 4477async fn test_manipulate_immutable_lines_with_single_selection(cx: &mut TestAppContext) {
 4478    init_test(cx, |_| {});
 4479
 4480    let mut cx = EditorTestContext::new(cx).await;
 4481
 4482    // Test sort_lines_case_insensitive()
 4483    cx.set_state(indoc! {"
 4484        «z
 4485        y
 4486        x
 4487        Z
 4488        Y
 4489        Xˇ»
 4490    "});
 4491    cx.update_editor(|e, window, cx| {
 4492        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 4493    });
 4494    cx.assert_editor_state(indoc! {"
 4495        «x
 4496        X
 4497        y
 4498        Y
 4499        z
 4500        Zˇ»
 4501    "});
 4502
 4503    // Test sort_lines_by_length()
 4504    //
 4505    // Demonstrates:
 4506    // - ∞ is 3 bytes UTF-8, but sorted by its char count (1)
 4507    // - sort is stable
 4508    cx.set_state(indoc! {"
 4509        «123
 4510        æ
 4511        12
 4512 4513        1
 4514        æˇ»
 4515    "});
 4516    cx.update_editor(|e, window, cx| e.sort_lines_by_length(&SortLinesByLength, window, cx));
 4517    cx.assert_editor_state(indoc! {"
 4518        «æ
 4519 4520        1
 4521        æ
 4522        12
 4523        123ˇ»
 4524    "});
 4525
 4526    // Test reverse_lines()
 4527    cx.set_state(indoc! {"
 4528        «5
 4529        4
 4530        3
 4531        2
 4532        1ˇ»
 4533    "});
 4534    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4535    cx.assert_editor_state(indoc! {"
 4536        «1
 4537        2
 4538        3
 4539        4
 4540        5ˇ»
 4541    "});
 4542
 4543    // Skip testing shuffle_line()
 4544
 4545    // From here on out, test more complex cases of manipulate_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 4546    // Since all methods calling manipulate_immutable_lines() are doing the exact same general thing (reordering lines)
 4547
 4548    // Don't manipulate when cursor is on single line, but expand the selection
 4549    cx.set_state(indoc! {"
 4550        ddˇdd
 4551        ccc
 4552        bb
 4553        a
 4554    "});
 4555    cx.update_editor(|e, window, cx| {
 4556        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4557    });
 4558    cx.assert_editor_state(indoc! {"
 4559        «ddddˇ»
 4560        ccc
 4561        bb
 4562        a
 4563    "});
 4564
 4565    // Basic manipulate case
 4566    // Start selection moves to column 0
 4567    // End of selection shrinks to fit shorter line
 4568    cx.set_state(indoc! {"
 4569        dd«d
 4570        ccc
 4571        bb
 4572        aaaaaˇ»
 4573    "});
 4574    cx.update_editor(|e, window, cx| {
 4575        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4576    });
 4577    cx.assert_editor_state(indoc! {"
 4578        «aaaaa
 4579        bb
 4580        ccc
 4581        dddˇ»
 4582    "});
 4583
 4584    // Manipulate case with newlines
 4585    cx.set_state(indoc! {"
 4586        dd«d
 4587        ccc
 4588
 4589        bb
 4590        aaaaa
 4591
 4592        ˇ»
 4593    "});
 4594    cx.update_editor(|e, window, cx| {
 4595        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4596    });
 4597    cx.assert_editor_state(indoc! {"
 4598        «
 4599
 4600        aaaaa
 4601        bb
 4602        ccc
 4603        dddˇ»
 4604
 4605    "});
 4606
 4607    // Adding new line
 4608    cx.set_state(indoc! {"
 4609        aa«a
 4610        bbˇ»b
 4611    "});
 4612    cx.update_editor(|e, window, cx| {
 4613        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added_line"))
 4614    });
 4615    cx.assert_editor_state(indoc! {"
 4616        «aaa
 4617        bbb
 4618        added_lineˇ»
 4619    "});
 4620
 4621    // Removing line
 4622    cx.set_state(indoc! {"
 4623        aa«a
 4624        bbbˇ»
 4625    "});
 4626    cx.update_editor(|e, window, cx| {
 4627        e.manipulate_immutable_lines(window, cx, |lines| {
 4628            lines.pop();
 4629        })
 4630    });
 4631    cx.assert_editor_state(indoc! {"
 4632        «aaaˇ»
 4633    "});
 4634
 4635    // Removing all lines
 4636    cx.set_state(indoc! {"
 4637        aa«a
 4638        bbbˇ»
 4639    "});
 4640    cx.update_editor(|e, window, cx| {
 4641        e.manipulate_immutable_lines(window, cx, |lines| {
 4642            lines.drain(..);
 4643        })
 4644    });
 4645    cx.assert_editor_state(indoc! {"
 4646        ˇ
 4647    "});
 4648}
 4649
 4650#[gpui::test]
 4651async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 4652    init_test(cx, |_| {});
 4653
 4654    let mut cx = EditorTestContext::new(cx).await;
 4655
 4656    // Consider continuous selection as single selection
 4657    cx.set_state(indoc! {"
 4658        Aaa«aa
 4659        cˇ»c«c
 4660        bb
 4661        aaaˇ»aa
 4662    "});
 4663    cx.update_editor(|e, window, cx| {
 4664        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4665    });
 4666    cx.assert_editor_state(indoc! {"
 4667        «Aaaaa
 4668        ccc
 4669        bb
 4670        aaaaaˇ»
 4671    "});
 4672
 4673    cx.set_state(indoc! {"
 4674        Aaa«aa
 4675        cˇ»c«c
 4676        bb
 4677        aaaˇ»aa
 4678    "});
 4679    cx.update_editor(|e, window, cx| {
 4680        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4681    });
 4682    cx.assert_editor_state(indoc! {"
 4683        «Aaaaa
 4684        ccc
 4685        bbˇ»
 4686    "});
 4687
 4688    // Consider non continuous selection as distinct dedup operations
 4689    cx.set_state(indoc! {"
 4690        «aaaaa
 4691        bb
 4692        aaaaa
 4693        aaaaaˇ»
 4694
 4695        aaa«aaˇ»
 4696    "});
 4697    cx.update_editor(|e, window, cx| {
 4698        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4699    });
 4700    cx.assert_editor_state(indoc! {"
 4701        «aaaaa
 4702        bbˇ»
 4703
 4704        «aaaaaˇ»
 4705    "});
 4706}
 4707
 4708#[gpui::test]
 4709async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 4710    init_test(cx, |_| {});
 4711
 4712    let mut cx = EditorTestContext::new(cx).await;
 4713
 4714    cx.set_state(indoc! {"
 4715        «Aaa
 4716        aAa
 4717        Aaaˇ»
 4718    "});
 4719    cx.update_editor(|e, window, cx| {
 4720        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4721    });
 4722    cx.assert_editor_state(indoc! {"
 4723        «Aaa
 4724        aAaˇ»
 4725    "});
 4726
 4727    cx.set_state(indoc! {"
 4728        «Aaa
 4729        aAa
 4730        aaAˇ»
 4731    "});
 4732    cx.update_editor(|e, window, cx| {
 4733        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4734    });
 4735    cx.assert_editor_state(indoc! {"
 4736        «Aaaˇ»
 4737    "});
 4738}
 4739
 4740#[gpui::test]
 4741async fn test_wrap_in_tag_single_selection(cx: &mut TestAppContext) {
 4742    init_test(cx, |_| {});
 4743
 4744    let mut cx = EditorTestContext::new(cx).await;
 4745
 4746    let js_language = Arc::new(Language::new(
 4747        LanguageConfig {
 4748            name: "JavaScript".into(),
 4749            wrap_characters: Some(language::WrapCharactersConfig {
 4750                start_prefix: "<".into(),
 4751                start_suffix: ">".into(),
 4752                end_prefix: "</".into(),
 4753                end_suffix: ">".into(),
 4754            }),
 4755            ..LanguageConfig::default()
 4756        },
 4757        None,
 4758    ));
 4759
 4760    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 4761
 4762    cx.set_state(indoc! {"
 4763        «testˇ»
 4764    "});
 4765    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4766    cx.assert_editor_state(indoc! {"
 4767        <«ˇ»>test</«ˇ»>
 4768    "});
 4769
 4770    cx.set_state(indoc! {"
 4771        «test
 4772         testˇ»
 4773    "});
 4774    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4775    cx.assert_editor_state(indoc! {"
 4776        <«ˇ»>test
 4777         test</«ˇ»>
 4778    "});
 4779
 4780    cx.set_state(indoc! {"
 4781        teˇst
 4782    "});
 4783    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4784    cx.assert_editor_state(indoc! {"
 4785        te<«ˇ»></«ˇ»>st
 4786    "});
 4787}
 4788
 4789#[gpui::test]
 4790async fn test_wrap_in_tag_multi_selection(cx: &mut TestAppContext) {
 4791    init_test(cx, |_| {});
 4792
 4793    let mut cx = EditorTestContext::new(cx).await;
 4794
 4795    let js_language = Arc::new(Language::new(
 4796        LanguageConfig {
 4797            name: "JavaScript".into(),
 4798            wrap_characters: Some(language::WrapCharactersConfig {
 4799                start_prefix: "<".into(),
 4800                start_suffix: ">".into(),
 4801                end_prefix: "</".into(),
 4802                end_suffix: ">".into(),
 4803            }),
 4804            ..LanguageConfig::default()
 4805        },
 4806        None,
 4807    ));
 4808
 4809    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 4810
 4811    cx.set_state(indoc! {"
 4812        «testˇ»
 4813        «testˇ» «testˇ»
 4814        «testˇ»
 4815    "});
 4816    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4817    cx.assert_editor_state(indoc! {"
 4818        <«ˇ»>test</«ˇ»>
 4819        <«ˇ»>test</«ˇ»> <«ˇ»>test</«ˇ»>
 4820        <«ˇ»>test</«ˇ»>
 4821    "});
 4822
 4823    cx.set_state(indoc! {"
 4824        «test
 4825         testˇ»
 4826        «test
 4827         testˇ»
 4828    "});
 4829    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4830    cx.assert_editor_state(indoc! {"
 4831        <«ˇ»>test
 4832         test</«ˇ»>
 4833        <«ˇ»>test
 4834         test</«ˇ»>
 4835    "});
 4836}
 4837
 4838#[gpui::test]
 4839async fn test_wrap_in_tag_does_nothing_in_unsupported_languages(cx: &mut TestAppContext) {
 4840    init_test(cx, |_| {});
 4841
 4842    let mut cx = EditorTestContext::new(cx).await;
 4843
 4844    let plaintext_language = Arc::new(Language::new(
 4845        LanguageConfig {
 4846            name: "Plain Text".into(),
 4847            ..LanguageConfig::default()
 4848        },
 4849        None,
 4850    ));
 4851
 4852    cx.update_buffer(|buffer, cx| buffer.set_language(Some(plaintext_language), cx));
 4853
 4854    cx.set_state(indoc! {"
 4855        «testˇ»
 4856    "});
 4857    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4858    cx.assert_editor_state(indoc! {"
 4859      «testˇ»
 4860    "});
 4861}
 4862
 4863#[gpui::test]
 4864async fn test_manipulate_immutable_lines_with_multi_selection(cx: &mut TestAppContext) {
 4865    init_test(cx, |_| {});
 4866
 4867    let mut cx = EditorTestContext::new(cx).await;
 4868
 4869    // Manipulate with multiple selections on a single line
 4870    cx.set_state(indoc! {"
 4871        dd«dd
 4872        cˇ»c«c
 4873        bb
 4874        aaaˇ»aa
 4875    "});
 4876    cx.update_editor(|e, window, cx| {
 4877        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4878    });
 4879    cx.assert_editor_state(indoc! {"
 4880        «aaaaa
 4881        bb
 4882        ccc
 4883        ddddˇ»
 4884    "});
 4885
 4886    // Manipulate with multiple disjoin selections
 4887    cx.set_state(indoc! {"
 4888 4889        4
 4890        3
 4891        2
 4892        1ˇ»
 4893
 4894        dd«dd
 4895        ccc
 4896        bb
 4897        aaaˇ»aa
 4898    "});
 4899    cx.update_editor(|e, window, cx| {
 4900        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4901    });
 4902    cx.assert_editor_state(indoc! {"
 4903        «1
 4904        2
 4905        3
 4906        4
 4907        5ˇ»
 4908
 4909        «aaaaa
 4910        bb
 4911        ccc
 4912        ddddˇ»
 4913    "});
 4914
 4915    // Adding lines on each selection
 4916    cx.set_state(indoc! {"
 4917 4918        1ˇ»
 4919
 4920        bb«bb
 4921        aaaˇ»aa
 4922    "});
 4923    cx.update_editor(|e, window, cx| {
 4924        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added line"))
 4925    });
 4926    cx.assert_editor_state(indoc! {"
 4927        «2
 4928        1
 4929        added lineˇ»
 4930
 4931        «bbbb
 4932        aaaaa
 4933        added lineˇ»
 4934    "});
 4935
 4936    // Removing lines on each selection
 4937    cx.set_state(indoc! {"
 4938 4939        1ˇ»
 4940
 4941        bb«bb
 4942        aaaˇ»aa
 4943    "});
 4944    cx.update_editor(|e, window, cx| {
 4945        e.manipulate_immutable_lines(window, cx, |lines| {
 4946            lines.pop();
 4947        })
 4948    });
 4949    cx.assert_editor_state(indoc! {"
 4950        «2ˇ»
 4951
 4952        «bbbbˇ»
 4953    "});
 4954}
 4955
 4956#[gpui::test]
 4957async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 4958    init_test(cx, |settings| {
 4959        settings.defaults.tab_size = NonZeroU32::new(3)
 4960    });
 4961
 4962    let mut cx = EditorTestContext::new(cx).await;
 4963
 4964    // MULTI SELECTION
 4965    // Ln.1 "«" tests empty lines
 4966    // Ln.9 tests just leading whitespace
 4967    cx.set_state(indoc! {"
 4968        «
 4969        abc                 // No indentationˇ»
 4970        «\tabc              // 1 tabˇ»
 4971        \t\tabc «      ˇ»   // 2 tabs
 4972        \t ab«c             // Tab followed by space
 4973         \tabc              // Space followed by tab (3 spaces should be the result)
 4974        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4975           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 4976        \t
 4977        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4978    "});
 4979    cx.update_editor(|e, window, cx| {
 4980        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4981    });
 4982    cx.assert_editor_state(
 4983        indoc! {"
 4984            «
 4985            abc                 // No indentation
 4986               abc              // 1 tab
 4987                  abc          // 2 tabs
 4988                abc             // Tab followed by space
 4989               abc              // Space followed by tab (3 spaces should be the result)
 4990                           abc   // Mixed indentation (tab conversion depends on the column)
 4991               abc         // Already space indented
 4992               ·
 4993               abc\tdef          // Only the leading tab is manipulatedˇ»
 4994        "}
 4995        .replace("·", "")
 4996        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4997    );
 4998
 4999    // Test on just a few lines, the others should remain unchanged
 5000    // Only lines (3, 5, 10, 11) should change
 5001    cx.set_state(
 5002        indoc! {"
 5003            ·
 5004            abc                 // No indentation
 5005            \tabcˇ               // 1 tab
 5006            \t\tabc             // 2 tabs
 5007            \t abcˇ              // Tab followed by space
 5008             \tabc              // Space followed by tab (3 spaces should be the result)
 5009            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5010               abc              // Already space indented
 5011            «\t
 5012            \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5013        "}
 5014        .replace("·", "")
 5015        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5016    );
 5017    cx.update_editor(|e, window, cx| {
 5018        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5019    });
 5020    cx.assert_editor_state(
 5021        indoc! {"
 5022            ·
 5023            abc                 // No indentation
 5024            «   abc               // 1 tabˇ»
 5025            \t\tabc             // 2 tabs
 5026            «    abc              // Tab followed by spaceˇ»
 5027             \tabc              // Space followed by tab (3 spaces should be the result)
 5028            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5029               abc              // Already space indented
 5030            «   ·
 5031               abc\tdef          // Only the leading tab is manipulatedˇ»
 5032        "}
 5033        .replace("·", "")
 5034        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5035    );
 5036
 5037    // SINGLE SELECTION
 5038    // Ln.1 "«" tests empty lines
 5039    // Ln.9 tests just leading whitespace
 5040    cx.set_state(indoc! {"
 5041        «
 5042        abc                 // No indentation
 5043        \tabc               // 1 tab
 5044        \t\tabc             // 2 tabs
 5045        \t abc              // Tab followed by space
 5046         \tabc              // Space followed by tab (3 spaces should be the result)
 5047        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5048           abc              // Already space indented
 5049        \t
 5050        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5051    "});
 5052    cx.update_editor(|e, window, cx| {
 5053        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5054    });
 5055    cx.assert_editor_state(
 5056        indoc! {"
 5057            «
 5058            abc                 // No indentation
 5059               abc               // 1 tab
 5060                  abc             // 2 tabs
 5061                abc              // Tab followed by space
 5062               abc              // Space followed by tab (3 spaces should be the result)
 5063                           abc   // Mixed indentation (tab conversion depends on the column)
 5064               abc              // Already space indented
 5065               ·
 5066               abc\tdef          // Only the leading tab is manipulatedˇ»
 5067        "}
 5068        .replace("·", "")
 5069        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5070    );
 5071}
 5072
 5073#[gpui::test]
 5074async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 5075    init_test(cx, |settings| {
 5076        settings.defaults.tab_size = NonZeroU32::new(3)
 5077    });
 5078
 5079    let mut cx = EditorTestContext::new(cx).await;
 5080
 5081    // MULTI SELECTION
 5082    // Ln.1 "«" tests empty lines
 5083    // Ln.11 tests just leading whitespace
 5084    cx.set_state(indoc! {"
 5085        «
 5086        abˇ»ˇc                 // No indentation
 5087         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 5088          abc  «             // 2 spaces (< 3 so dont convert)
 5089           abc              // 3 spaces (convert)
 5090             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 5091        «\tˇ»\t«\tˇ»abc           // Already tab indented
 5092        «\t abc              // Tab followed by space
 5093         \tabc              // Space followed by tab (should be consumed due to tab)
 5094        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5095           \tˇ»  «\t
 5096           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 5097    "});
 5098    cx.update_editor(|e, window, cx| {
 5099        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5100    });
 5101    cx.assert_editor_state(indoc! {"
 5102        «
 5103        abc                 // No indentation
 5104         abc                // 1 space (< 3 so dont convert)
 5105          abc               // 2 spaces (< 3 so dont convert)
 5106        \tabc              // 3 spaces (convert)
 5107        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5108        \t\t\tabc           // Already tab indented
 5109        \t abc              // Tab followed by space
 5110        \tabc              // Space followed by tab (should be consumed due to tab)
 5111        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5112        \t\t\t
 5113        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5114    "});
 5115
 5116    // Test on just a few lines, the other should remain unchanged
 5117    // Only lines (4, 8, 11, 12) should change
 5118    cx.set_state(
 5119        indoc! {"
 5120            ·
 5121            abc                 // No indentation
 5122             abc                // 1 space (< 3 so dont convert)
 5123              abc               // 2 spaces (< 3 so dont convert)
 5124            «   abc              // 3 spaces (convert)ˇ»
 5125                 abc            // 5 spaces (1 tab + 2 spaces)
 5126            \t\t\tabc           // Already tab indented
 5127            \t abc              // Tab followed by space
 5128             \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 5129               \t\t  \tabc      // Mixed indentation
 5130            \t \t  \t   \tabc   // Mixed indentation
 5131               \t  \tˇ
 5132            «   abc   \t         // Only the leading spaces should be convertedˇ»
 5133        "}
 5134        .replace("·", "")
 5135        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5136    );
 5137    cx.update_editor(|e, window, cx| {
 5138        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5139    });
 5140    cx.assert_editor_state(
 5141        indoc! {"
 5142            ·
 5143            abc                 // No indentation
 5144             abc                // 1 space (< 3 so dont convert)
 5145              abc               // 2 spaces (< 3 so dont convert)
 5146            «\tabc              // 3 spaces (convert)ˇ»
 5147                 abc            // 5 spaces (1 tab + 2 spaces)
 5148            \t\t\tabc           // Already tab indented
 5149            \t abc              // Tab followed by space
 5150            «\tabc              // Space followed by tab (should be consumed due to tab)ˇ»
 5151               \t\t  \tabc      // Mixed indentation
 5152            \t \t  \t   \tabc   // Mixed indentation
 5153            «\t\t\t
 5154            \tabc   \t         // Only the leading spaces should be convertedˇ»
 5155        "}
 5156        .replace("·", "")
 5157        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5158    );
 5159
 5160    // SINGLE SELECTION
 5161    // Ln.1 "«" tests empty lines
 5162    // Ln.11 tests just leading whitespace
 5163    cx.set_state(indoc! {"
 5164        «
 5165        abc                 // No indentation
 5166         abc                // 1 space (< 3 so dont convert)
 5167          abc               // 2 spaces (< 3 so dont convert)
 5168           abc              // 3 spaces (convert)
 5169             abc            // 5 spaces (1 tab + 2 spaces)
 5170        \t\t\tabc           // Already tab indented
 5171        \t abc              // Tab followed by space
 5172         \tabc              // Space followed by tab (should be consumed due to tab)
 5173        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5174           \t  \t
 5175           abc   \t         // Only the leading spaces should be convertedˇ»
 5176    "});
 5177    cx.update_editor(|e, window, cx| {
 5178        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5179    });
 5180    cx.assert_editor_state(indoc! {"
 5181        «
 5182        abc                 // No indentation
 5183         abc                // 1 space (< 3 so dont convert)
 5184          abc               // 2 spaces (< 3 so dont convert)
 5185        \tabc              // 3 spaces (convert)
 5186        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5187        \t\t\tabc           // Already tab indented
 5188        \t abc              // Tab followed by space
 5189        \tabc              // Space followed by tab (should be consumed due to tab)
 5190        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5191        \t\t\t
 5192        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5193    "});
 5194}
 5195
 5196#[gpui::test]
 5197async fn test_toggle_case(cx: &mut TestAppContext) {
 5198    init_test(cx, |_| {});
 5199
 5200    let mut cx = EditorTestContext::new(cx).await;
 5201
 5202    // If all lower case -> upper case
 5203    cx.set_state(indoc! {"
 5204        «hello worldˇ»
 5205    "});
 5206    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5207    cx.assert_editor_state(indoc! {"
 5208        «HELLO WORLDˇ»
 5209    "});
 5210
 5211    // If all upper case -> lower case
 5212    cx.set_state(indoc! {"
 5213        «HELLO WORLDˇ»
 5214    "});
 5215    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5216    cx.assert_editor_state(indoc! {"
 5217        «hello worldˇ»
 5218    "});
 5219
 5220    // If any upper case characters are identified -> lower case
 5221    // This matches JetBrains IDEs
 5222    cx.set_state(indoc! {"
 5223        «hEllo worldˇ»
 5224    "});
 5225    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5226    cx.assert_editor_state(indoc! {"
 5227        «hello worldˇ»
 5228    "});
 5229}
 5230
 5231#[gpui::test]
 5232async fn test_convert_to_sentence_case(cx: &mut TestAppContext) {
 5233    init_test(cx, |_| {});
 5234
 5235    let mut cx = EditorTestContext::new(cx).await;
 5236
 5237    cx.set_state(indoc! {"
 5238        «implement-windows-supportˇ»
 5239    "});
 5240    cx.update_editor(|e, window, cx| {
 5241        e.convert_to_sentence_case(&ConvertToSentenceCase, window, cx)
 5242    });
 5243    cx.assert_editor_state(indoc! {"
 5244        «Implement windows supportˇ»
 5245    "});
 5246}
 5247
 5248#[gpui::test]
 5249async fn test_manipulate_text(cx: &mut TestAppContext) {
 5250    init_test(cx, |_| {});
 5251
 5252    let mut cx = EditorTestContext::new(cx).await;
 5253
 5254    // Test convert_to_upper_case()
 5255    cx.set_state(indoc! {"
 5256        «hello worldˇ»
 5257    "});
 5258    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5259    cx.assert_editor_state(indoc! {"
 5260        «HELLO WORLDˇ»
 5261    "});
 5262
 5263    // Test convert_to_lower_case()
 5264    cx.set_state(indoc! {"
 5265        «HELLO WORLDˇ»
 5266    "});
 5267    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 5268    cx.assert_editor_state(indoc! {"
 5269        «hello worldˇ»
 5270    "});
 5271
 5272    // Test multiple line, single selection case
 5273    cx.set_state(indoc! {"
 5274        «The quick brown
 5275        fox jumps over
 5276        the lazy dogˇ»
 5277    "});
 5278    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 5279    cx.assert_editor_state(indoc! {"
 5280        «The Quick Brown
 5281        Fox Jumps Over
 5282        The Lazy Dogˇ»
 5283    "});
 5284
 5285    // Test multiple line, single selection case
 5286    cx.set_state(indoc! {"
 5287        «The quick brown
 5288        fox jumps over
 5289        the lazy dogˇ»
 5290    "});
 5291    cx.update_editor(|e, window, cx| {
 5292        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 5293    });
 5294    cx.assert_editor_state(indoc! {"
 5295        «TheQuickBrown
 5296        FoxJumpsOver
 5297        TheLazyDogˇ»
 5298    "});
 5299
 5300    // From here on out, test more complex cases of manipulate_text()
 5301
 5302    // Test no selection case - should affect words cursors are in
 5303    // Cursor at beginning, middle, and end of word
 5304    cx.set_state(indoc! {"
 5305        ˇhello big beauˇtiful worldˇ
 5306    "});
 5307    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5308    cx.assert_editor_state(indoc! {"
 5309        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 5310    "});
 5311
 5312    // Test multiple selections on a single line and across multiple lines
 5313    cx.set_state(indoc! {"
 5314        «Theˇ» quick «brown
 5315        foxˇ» jumps «overˇ»
 5316        the «lazyˇ» dog
 5317    "});
 5318    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5319    cx.assert_editor_state(indoc! {"
 5320        «THEˇ» quick «BROWN
 5321        FOXˇ» jumps «OVERˇ»
 5322        the «LAZYˇ» dog
 5323    "});
 5324
 5325    // Test case where text length grows
 5326    cx.set_state(indoc! {"
 5327        «tschüߡ»
 5328    "});
 5329    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5330    cx.assert_editor_state(indoc! {"
 5331        «TSCHÜSSˇ»
 5332    "});
 5333
 5334    // Test to make sure we don't crash when text shrinks
 5335    cx.set_state(indoc! {"
 5336        aaa_bbbˇ
 5337    "});
 5338    cx.update_editor(|e, window, cx| {
 5339        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5340    });
 5341    cx.assert_editor_state(indoc! {"
 5342        «aaaBbbˇ»
 5343    "});
 5344
 5345    // Test to make sure we all aware of the fact that each word can grow and shrink
 5346    // Final selections should be aware of this fact
 5347    cx.set_state(indoc! {"
 5348        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 5349    "});
 5350    cx.update_editor(|e, window, cx| {
 5351        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5352    });
 5353    cx.assert_editor_state(indoc! {"
 5354        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 5355    "});
 5356
 5357    cx.set_state(indoc! {"
 5358        «hElLo, WoRld!ˇ»
 5359    "});
 5360    cx.update_editor(|e, window, cx| {
 5361        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 5362    });
 5363    cx.assert_editor_state(indoc! {"
 5364        «HeLlO, wOrLD!ˇ»
 5365    "});
 5366
 5367    // Test selections with `line_mode = true`.
 5368    cx.update_editor(|editor, _window, _cx| editor.selections.line_mode = true);
 5369    cx.set_state(indoc! {"
 5370        «The quick brown
 5371        fox jumps over
 5372        tˇ»he lazy dog
 5373    "});
 5374    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5375    cx.assert_editor_state(indoc! {"
 5376        «THE QUICK BROWN
 5377        FOX JUMPS OVER
 5378        THE LAZY DOGˇ»
 5379    "});
 5380}
 5381
 5382#[gpui::test]
 5383fn test_duplicate_line(cx: &mut TestAppContext) {
 5384    init_test(cx, |_| {});
 5385
 5386    let editor = cx.add_window(|window, cx| {
 5387        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5388        build_editor(buffer, window, cx)
 5389    });
 5390    _ = editor.update(cx, |editor, window, cx| {
 5391        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5392            s.select_display_ranges([
 5393                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5394                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5395                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5396                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5397            ])
 5398        });
 5399        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5400        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5401        assert_eq!(
 5402            editor.selections.display_ranges(cx),
 5403            vec![
 5404                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 5405                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 5406                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5407                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 5408            ]
 5409        );
 5410    });
 5411
 5412    let editor = cx.add_window(|window, cx| {
 5413        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5414        build_editor(buffer, window, cx)
 5415    });
 5416    _ = editor.update(cx, |editor, window, cx| {
 5417        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5418            s.select_display_ranges([
 5419                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5420                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5421            ])
 5422        });
 5423        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5424        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5425        assert_eq!(
 5426            editor.selections.display_ranges(cx),
 5427            vec![
 5428                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 5429                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 5430            ]
 5431        );
 5432    });
 5433
 5434    // With `move_upwards` the selections stay in place, except for
 5435    // the lines inserted above them
 5436    let editor = cx.add_window(|window, cx| {
 5437        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5438        build_editor(buffer, window, cx)
 5439    });
 5440    _ = editor.update(cx, |editor, window, cx| {
 5441        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5442            s.select_display_ranges([
 5443                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5444                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5445                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5446                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5447            ])
 5448        });
 5449        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5450        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5451        assert_eq!(
 5452            editor.selections.display_ranges(cx),
 5453            vec![
 5454                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5455                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5456                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 5457                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 5458            ]
 5459        );
 5460    });
 5461
 5462    let editor = cx.add_window(|window, cx| {
 5463        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5464        build_editor(buffer, window, cx)
 5465    });
 5466    _ = editor.update(cx, |editor, window, cx| {
 5467        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5468            s.select_display_ranges([
 5469                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5470                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5471            ])
 5472        });
 5473        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5474        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5475        assert_eq!(
 5476            editor.selections.display_ranges(cx),
 5477            vec![
 5478                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5479                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5480            ]
 5481        );
 5482    });
 5483
 5484    let editor = cx.add_window(|window, cx| {
 5485        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5486        build_editor(buffer, window, cx)
 5487    });
 5488    _ = editor.update(cx, |editor, window, cx| {
 5489        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5490            s.select_display_ranges([
 5491                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5492                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5493            ])
 5494        });
 5495        editor.duplicate_selection(&DuplicateSelection, window, cx);
 5496        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 5497        assert_eq!(
 5498            editor.selections.display_ranges(cx),
 5499            vec![
 5500                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5501                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 5502            ]
 5503        );
 5504    });
 5505}
 5506
 5507#[gpui::test]
 5508fn test_move_line_up_down(cx: &mut TestAppContext) {
 5509    init_test(cx, |_| {});
 5510
 5511    let editor = cx.add_window(|window, cx| {
 5512        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5513        build_editor(buffer, window, cx)
 5514    });
 5515    _ = editor.update(cx, |editor, window, cx| {
 5516        editor.fold_creases(
 5517            vec![
 5518                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 5519                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 5520                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 5521            ],
 5522            true,
 5523            window,
 5524            cx,
 5525        );
 5526        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5527            s.select_display_ranges([
 5528                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5529                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5530                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5531                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 5532            ])
 5533        });
 5534        assert_eq!(
 5535            editor.display_text(cx),
 5536            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 5537        );
 5538
 5539        editor.move_line_up(&MoveLineUp, window, cx);
 5540        assert_eq!(
 5541            editor.display_text(cx),
 5542            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 5543        );
 5544        assert_eq!(
 5545            editor.selections.display_ranges(cx),
 5546            vec![
 5547                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5548                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5549                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5550                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5551            ]
 5552        );
 5553    });
 5554
 5555    _ = editor.update(cx, |editor, window, cx| {
 5556        editor.move_line_down(&MoveLineDown, window, cx);
 5557        assert_eq!(
 5558            editor.display_text(cx),
 5559            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 5560        );
 5561        assert_eq!(
 5562            editor.selections.display_ranges(cx),
 5563            vec![
 5564                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5565                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5566                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5567                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5568            ]
 5569        );
 5570    });
 5571
 5572    _ = editor.update(cx, |editor, window, cx| {
 5573        editor.move_line_down(&MoveLineDown, window, cx);
 5574        assert_eq!(
 5575            editor.display_text(cx),
 5576            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 5577        );
 5578        assert_eq!(
 5579            editor.selections.display_ranges(cx),
 5580            vec![
 5581                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5582                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5583                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5584                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5585            ]
 5586        );
 5587    });
 5588
 5589    _ = editor.update(cx, |editor, window, cx| {
 5590        editor.move_line_up(&MoveLineUp, window, cx);
 5591        assert_eq!(
 5592            editor.display_text(cx),
 5593            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 5594        );
 5595        assert_eq!(
 5596            editor.selections.display_ranges(cx),
 5597            vec![
 5598                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5599                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5600                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5601                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5602            ]
 5603        );
 5604    });
 5605}
 5606
 5607#[gpui::test]
 5608fn test_move_line_up_selection_at_end_of_fold(cx: &mut TestAppContext) {
 5609    init_test(cx, |_| {});
 5610    let editor = cx.add_window(|window, cx| {
 5611        let buffer = MultiBuffer::build_simple("\n\n\n\n\n\naaaa\nbbbb\ncccc", cx);
 5612        build_editor(buffer, window, cx)
 5613    });
 5614    _ = editor.update(cx, |editor, window, cx| {
 5615        editor.fold_creases(
 5616            vec![Crease::simple(
 5617                Point::new(6, 4)..Point::new(7, 4),
 5618                FoldPlaceholder::test(),
 5619            )],
 5620            true,
 5621            window,
 5622            cx,
 5623        );
 5624        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5625            s.select_ranges([Point::new(7, 4)..Point::new(7, 4)])
 5626        });
 5627        assert_eq!(editor.display_text(cx), "\n\n\n\n\n\naaaa⋯\ncccc");
 5628        editor.move_line_up(&MoveLineUp, window, cx);
 5629        let buffer_text = editor.buffer.read(cx).snapshot(cx).text();
 5630        assert_eq!(buffer_text, "\n\n\n\n\naaaa\nbbbb\n\ncccc");
 5631    });
 5632}
 5633
 5634#[gpui::test]
 5635fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 5636    init_test(cx, |_| {});
 5637
 5638    let editor = cx.add_window(|window, cx| {
 5639        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5640        build_editor(buffer, window, cx)
 5641    });
 5642    _ = editor.update(cx, |editor, window, cx| {
 5643        let snapshot = editor.buffer.read(cx).snapshot(cx);
 5644        editor.insert_blocks(
 5645            [BlockProperties {
 5646                style: BlockStyle::Fixed,
 5647                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 5648                height: Some(1),
 5649                render: Arc::new(|_| div().into_any()),
 5650                priority: 0,
 5651            }],
 5652            Some(Autoscroll::fit()),
 5653            cx,
 5654        );
 5655        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5656            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 5657        });
 5658        editor.move_line_down(&MoveLineDown, window, cx);
 5659    });
 5660}
 5661
 5662#[gpui::test]
 5663async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 5664    init_test(cx, |_| {});
 5665
 5666    let mut cx = EditorTestContext::new(cx).await;
 5667    cx.set_state(
 5668        &"
 5669            ˇzero
 5670            one
 5671            two
 5672            three
 5673            four
 5674            five
 5675        "
 5676        .unindent(),
 5677    );
 5678
 5679    // Create a four-line block that replaces three lines of text.
 5680    cx.update_editor(|editor, window, cx| {
 5681        let snapshot = editor.snapshot(window, cx);
 5682        let snapshot = &snapshot.buffer_snapshot;
 5683        let placement = BlockPlacement::Replace(
 5684            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 5685        );
 5686        editor.insert_blocks(
 5687            [BlockProperties {
 5688                placement,
 5689                height: Some(4),
 5690                style: BlockStyle::Sticky,
 5691                render: Arc::new(|_| gpui::div().into_any_element()),
 5692                priority: 0,
 5693            }],
 5694            None,
 5695            cx,
 5696        );
 5697    });
 5698
 5699    // Move down so that the cursor touches the block.
 5700    cx.update_editor(|editor, window, cx| {
 5701        editor.move_down(&Default::default(), window, cx);
 5702    });
 5703    cx.assert_editor_state(
 5704        &"
 5705            zero
 5706            «one
 5707            two
 5708            threeˇ»
 5709            four
 5710            five
 5711        "
 5712        .unindent(),
 5713    );
 5714
 5715    // Move down past the block.
 5716    cx.update_editor(|editor, window, cx| {
 5717        editor.move_down(&Default::default(), window, cx);
 5718    });
 5719    cx.assert_editor_state(
 5720        &"
 5721            zero
 5722            one
 5723            two
 5724            three
 5725            ˇfour
 5726            five
 5727        "
 5728        .unindent(),
 5729    );
 5730}
 5731
 5732#[gpui::test]
 5733fn test_transpose(cx: &mut TestAppContext) {
 5734    init_test(cx, |_| {});
 5735
 5736    _ = cx.add_window(|window, cx| {
 5737        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 5738        editor.set_style(EditorStyle::default(), window, cx);
 5739        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5740            s.select_ranges([1..1])
 5741        });
 5742        editor.transpose(&Default::default(), window, cx);
 5743        assert_eq!(editor.text(cx), "bac");
 5744        assert_eq!(editor.selections.ranges(cx), [2..2]);
 5745
 5746        editor.transpose(&Default::default(), window, cx);
 5747        assert_eq!(editor.text(cx), "bca");
 5748        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5749
 5750        editor.transpose(&Default::default(), window, cx);
 5751        assert_eq!(editor.text(cx), "bac");
 5752        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5753
 5754        editor
 5755    });
 5756
 5757    _ = cx.add_window(|window, cx| {
 5758        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5759        editor.set_style(EditorStyle::default(), window, cx);
 5760        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5761            s.select_ranges([3..3])
 5762        });
 5763        editor.transpose(&Default::default(), window, cx);
 5764        assert_eq!(editor.text(cx), "acb\nde");
 5765        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5766
 5767        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5768            s.select_ranges([4..4])
 5769        });
 5770        editor.transpose(&Default::default(), window, cx);
 5771        assert_eq!(editor.text(cx), "acbd\ne");
 5772        assert_eq!(editor.selections.ranges(cx), [5..5]);
 5773
 5774        editor.transpose(&Default::default(), window, cx);
 5775        assert_eq!(editor.text(cx), "acbde\n");
 5776        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5777
 5778        editor.transpose(&Default::default(), window, cx);
 5779        assert_eq!(editor.text(cx), "acbd\ne");
 5780        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5781
 5782        editor
 5783    });
 5784
 5785    _ = cx.add_window(|window, cx| {
 5786        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5787        editor.set_style(EditorStyle::default(), window, cx);
 5788        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5789            s.select_ranges([1..1, 2..2, 4..4])
 5790        });
 5791        editor.transpose(&Default::default(), window, cx);
 5792        assert_eq!(editor.text(cx), "bacd\ne");
 5793        assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
 5794
 5795        editor.transpose(&Default::default(), window, cx);
 5796        assert_eq!(editor.text(cx), "bcade\n");
 5797        assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
 5798
 5799        editor.transpose(&Default::default(), window, cx);
 5800        assert_eq!(editor.text(cx), "bcda\ne");
 5801        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5802
 5803        editor.transpose(&Default::default(), window, cx);
 5804        assert_eq!(editor.text(cx), "bcade\n");
 5805        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5806
 5807        editor.transpose(&Default::default(), window, cx);
 5808        assert_eq!(editor.text(cx), "bcaed\n");
 5809        assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
 5810
 5811        editor
 5812    });
 5813
 5814    _ = cx.add_window(|window, cx| {
 5815        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 5816        editor.set_style(EditorStyle::default(), window, cx);
 5817        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5818            s.select_ranges([4..4])
 5819        });
 5820        editor.transpose(&Default::default(), window, cx);
 5821        assert_eq!(editor.text(cx), "🏀🍐✋");
 5822        assert_eq!(editor.selections.ranges(cx), [8..8]);
 5823
 5824        editor.transpose(&Default::default(), window, cx);
 5825        assert_eq!(editor.text(cx), "🏀✋🍐");
 5826        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5827
 5828        editor.transpose(&Default::default(), window, cx);
 5829        assert_eq!(editor.text(cx), "🏀🍐✋");
 5830        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5831
 5832        editor
 5833    });
 5834}
 5835
 5836#[gpui::test]
 5837async fn test_rewrap(cx: &mut TestAppContext) {
 5838    init_test(cx, |settings| {
 5839        settings.languages.0.extend([
 5840            (
 5841                "Markdown".into(),
 5842                LanguageSettingsContent {
 5843                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5844                    preferred_line_length: Some(40),
 5845                    ..Default::default()
 5846                },
 5847            ),
 5848            (
 5849                "Plain Text".into(),
 5850                LanguageSettingsContent {
 5851                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5852                    preferred_line_length: Some(40),
 5853                    ..Default::default()
 5854                },
 5855            ),
 5856            (
 5857                "C++".into(),
 5858                LanguageSettingsContent {
 5859                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5860                    preferred_line_length: Some(40),
 5861                    ..Default::default()
 5862                },
 5863            ),
 5864            (
 5865                "Python".into(),
 5866                LanguageSettingsContent {
 5867                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5868                    preferred_line_length: Some(40),
 5869                    ..Default::default()
 5870                },
 5871            ),
 5872            (
 5873                "Rust".into(),
 5874                LanguageSettingsContent {
 5875                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5876                    preferred_line_length: Some(40),
 5877                    ..Default::default()
 5878                },
 5879            ),
 5880        ])
 5881    });
 5882
 5883    let mut cx = EditorTestContext::new(cx).await;
 5884
 5885    let cpp_language = Arc::new(Language::new(
 5886        LanguageConfig {
 5887            name: "C++".into(),
 5888            line_comments: vec!["// ".into()],
 5889            ..LanguageConfig::default()
 5890        },
 5891        None,
 5892    ));
 5893    let python_language = Arc::new(Language::new(
 5894        LanguageConfig {
 5895            name: "Python".into(),
 5896            line_comments: vec!["# ".into()],
 5897            ..LanguageConfig::default()
 5898        },
 5899        None,
 5900    ));
 5901    let markdown_language = Arc::new(Language::new(
 5902        LanguageConfig {
 5903            name: "Markdown".into(),
 5904            rewrap_prefixes: vec![
 5905                regex::Regex::new("\\d+\\.\\s+").unwrap(),
 5906                regex::Regex::new("[-*+]\\s+").unwrap(),
 5907            ],
 5908            ..LanguageConfig::default()
 5909        },
 5910        None,
 5911    ));
 5912    let rust_language = Arc::new(
 5913        Language::new(
 5914            LanguageConfig {
 5915                name: "Rust".into(),
 5916                line_comments: vec!["// ".into(), "/// ".into()],
 5917                ..LanguageConfig::default()
 5918            },
 5919            Some(tree_sitter_rust::LANGUAGE.into()),
 5920        )
 5921        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 5922        .unwrap(),
 5923    );
 5924
 5925    let plaintext_language = Arc::new(Language::new(
 5926        LanguageConfig {
 5927            name: "Plain Text".into(),
 5928            ..LanguageConfig::default()
 5929        },
 5930        None,
 5931    ));
 5932
 5933    // Test basic rewrapping of a long line with a cursor
 5934    assert_rewrap(
 5935        indoc! {"
 5936            // ˇThis is a long comment that needs to be wrapped.
 5937        "},
 5938        indoc! {"
 5939            // ˇThis is a long comment that needs to
 5940            // be wrapped.
 5941        "},
 5942        cpp_language.clone(),
 5943        &mut cx,
 5944    );
 5945
 5946    // Test rewrapping a full selection
 5947    assert_rewrap(
 5948        indoc! {"
 5949            «// This selected long comment needs to be wrapped.ˇ»"
 5950        },
 5951        indoc! {"
 5952            «// This selected long comment needs to
 5953            // be wrapped.ˇ»"
 5954        },
 5955        cpp_language.clone(),
 5956        &mut cx,
 5957    );
 5958
 5959    // Test multiple cursors on different lines within the same paragraph are preserved after rewrapping
 5960    assert_rewrap(
 5961        indoc! {"
 5962            // ˇThis is the first line.
 5963            // Thisˇ is the second line.
 5964            // This is the thirdˇ line, all part of one paragraph.
 5965         "},
 5966        indoc! {"
 5967            // ˇThis is the first line. Thisˇ is the
 5968            // second line. This is the thirdˇ line,
 5969            // all part of one paragraph.
 5970         "},
 5971        cpp_language.clone(),
 5972        &mut cx,
 5973    );
 5974
 5975    // Test multiple cursors in different paragraphs trigger separate rewraps
 5976    assert_rewrap(
 5977        indoc! {"
 5978            // ˇThis is the first paragraph, first line.
 5979            // ˇThis is the first paragraph, second line.
 5980
 5981            // ˇThis is the second paragraph, first line.
 5982            // ˇThis is the second paragraph, second line.
 5983        "},
 5984        indoc! {"
 5985            // ˇThis is the first paragraph, first
 5986            // line. ˇThis is the first paragraph,
 5987            // second line.
 5988
 5989            // ˇThis is the second paragraph, first
 5990            // line. ˇThis is the second paragraph,
 5991            // second line.
 5992        "},
 5993        cpp_language.clone(),
 5994        &mut cx,
 5995    );
 5996
 5997    // Test that change in comment prefix (e.g., `//` to `///`) trigger seperate rewraps
 5998    assert_rewrap(
 5999        indoc! {"
 6000            «// A regular long long comment to be wrapped.
 6001            /// A documentation long comment to be wrapped.ˇ»
 6002          "},
 6003        indoc! {"
 6004            «// A regular long long comment to be
 6005            // wrapped.
 6006            /// A documentation long comment to be
 6007            /// wrapped.ˇ»
 6008          "},
 6009        rust_language.clone(),
 6010        &mut cx,
 6011    );
 6012
 6013    // Test that change in indentation level trigger seperate rewraps
 6014    assert_rewrap(
 6015        indoc! {"
 6016            fn foo() {
 6017                «// This is a long comment at the base indent.
 6018                    // This is a long comment at the next indent.ˇ»
 6019            }
 6020        "},
 6021        indoc! {"
 6022            fn foo() {
 6023                «// This is a long comment at the
 6024                // base indent.
 6025                    // This is a long comment at the
 6026                    // next indent.ˇ»
 6027            }
 6028        "},
 6029        rust_language.clone(),
 6030        &mut cx,
 6031    );
 6032
 6033    // Test that different comment prefix characters (e.g., '#') are handled correctly
 6034    assert_rewrap(
 6035        indoc! {"
 6036            # ˇThis is a long comment using a pound sign.
 6037        "},
 6038        indoc! {"
 6039            # ˇThis is a long comment using a pound
 6040            # sign.
 6041        "},
 6042        python_language,
 6043        &mut cx,
 6044    );
 6045
 6046    // Test rewrapping only affects comments, not code even when selected
 6047    assert_rewrap(
 6048        indoc! {"
 6049            «/// This doc comment is long and should be wrapped.
 6050            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6051        "},
 6052        indoc! {"
 6053            «/// This doc comment is long and should
 6054            /// be wrapped.
 6055            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6056        "},
 6057        rust_language.clone(),
 6058        &mut cx,
 6059    );
 6060
 6061    // Test that rewrapping works in Markdown documents where `allow_rewrap` is `Anywhere`
 6062    assert_rewrap(
 6063        indoc! {"
 6064            # Header
 6065
 6066            A long long long line of markdown text to wrap.ˇ
 6067         "},
 6068        indoc! {"
 6069            # Header
 6070
 6071            A long long long line of markdown text
 6072            to wrap.ˇ
 6073         "},
 6074        markdown_language.clone(),
 6075        &mut cx,
 6076    );
 6077
 6078    // Test that rewrapping boundary works and preserves relative indent for Markdown documents
 6079    assert_rewrap(
 6080        indoc! {"
 6081            «1. This is a numbered list item that is very long and needs to be wrapped properly.
 6082            2. This is a numbered list item that is very long and needs to be wrapped properly.
 6083            - This is an unordered list item that is also very long and should not merge with the numbered item.ˇ»
 6084        "},
 6085        indoc! {"
 6086            «1. This is a numbered list item that is
 6087               very long and needs to be wrapped
 6088               properly.
 6089            2. This is a numbered list item that is
 6090               very long and needs to be wrapped
 6091               properly.
 6092            - This is an unordered list item that is
 6093              also very long and should not merge
 6094              with the numbered item.ˇ»
 6095        "},
 6096        markdown_language.clone(),
 6097        &mut cx,
 6098    );
 6099
 6100    // Test that rewrapping add indents for rewrapping boundary if not exists already.
 6101    assert_rewrap(
 6102        indoc! {"
 6103            «1. This is a numbered list item that is
 6104            very long and needs to be wrapped
 6105            properly.
 6106            2. This is a numbered list item that is
 6107            very long and needs to be wrapped
 6108            properly.
 6109            - This is an unordered list item that is
 6110            also very long and should not merge with
 6111            the numbered item.ˇ»
 6112        "},
 6113        indoc! {"
 6114            «1. This is a numbered list item that is
 6115               very long and needs to be wrapped
 6116               properly.
 6117            2. This is a numbered list item that is
 6118               very long and needs to be wrapped
 6119               properly.
 6120            - This is an unordered list item that is
 6121              also very long and should not merge
 6122              with the numbered item.ˇ»
 6123        "},
 6124        markdown_language.clone(),
 6125        &mut cx,
 6126    );
 6127
 6128    // Test that rewrapping maintain indents even when they already exists.
 6129    assert_rewrap(
 6130        indoc! {"
 6131            «1. This is a numbered list
 6132               item that is very long and needs to be wrapped properly.
 6133            2. This is a numbered list
 6134               item that is very long and needs to be wrapped properly.
 6135            - This is an unordered list item that is also very long and
 6136              should not merge with the numbered item.ˇ»
 6137        "},
 6138        indoc! {"
 6139            «1. This is a numbered list item that is
 6140               very long and needs to be wrapped
 6141               properly.
 6142            2. This is a numbered list item that is
 6143               very long and needs to be wrapped
 6144               properly.
 6145            - This is an unordered list item that is
 6146              also very long and should not merge
 6147              with the numbered item.ˇ»
 6148        "},
 6149        markdown_language,
 6150        &mut cx,
 6151    );
 6152
 6153    // Test that rewrapping works in plain text where `allow_rewrap` is `Anywhere`
 6154    assert_rewrap(
 6155        indoc! {"
 6156            ˇThis is a very long line of plain text that will be wrapped.
 6157        "},
 6158        indoc! {"
 6159            ˇThis is a very long line of plain text
 6160            that will be wrapped.
 6161        "},
 6162        plaintext_language.clone(),
 6163        &mut cx,
 6164    );
 6165
 6166    // Test that non-commented code acts as a paragraph boundary within a selection
 6167    assert_rewrap(
 6168        indoc! {"
 6169               «// This is the first long comment block to be wrapped.
 6170               fn my_func(a: u32);
 6171               // This is the second long comment block to be wrapped.ˇ»
 6172           "},
 6173        indoc! {"
 6174               «// This is the first long comment block
 6175               // to be wrapped.
 6176               fn my_func(a: u32);
 6177               // This is the second long comment block
 6178               // to be wrapped.ˇ»
 6179           "},
 6180        rust_language,
 6181        &mut cx,
 6182    );
 6183
 6184    // Test rewrapping multiple selections, including ones with blank lines or tabs
 6185    assert_rewrap(
 6186        indoc! {"
 6187            «ˇThis is a very long line that will be wrapped.
 6188
 6189            This is another paragraph in the same selection.»
 6190
 6191            «\tThis is a very long indented line that will be wrapped.ˇ»
 6192         "},
 6193        indoc! {"
 6194            «ˇThis is a very long line that will be
 6195            wrapped.
 6196
 6197            This is another paragraph in the same
 6198            selection.»
 6199
 6200            «\tThis is a very long indented line
 6201            \tthat will be wrapped.ˇ»
 6202         "},
 6203        plaintext_language,
 6204        &mut cx,
 6205    );
 6206
 6207    // Test that an empty comment line acts as a paragraph boundary
 6208    assert_rewrap(
 6209        indoc! {"
 6210            // ˇThis is a long comment that will be wrapped.
 6211            //
 6212            // And this is another long comment that will also be wrapped.ˇ
 6213         "},
 6214        indoc! {"
 6215            // ˇThis is a long comment that will be
 6216            // wrapped.
 6217            //
 6218            // And this is another long comment that
 6219            // will also be wrapped.ˇ
 6220         "},
 6221        cpp_language,
 6222        &mut cx,
 6223    );
 6224
 6225    #[track_caller]
 6226    fn assert_rewrap(
 6227        unwrapped_text: &str,
 6228        wrapped_text: &str,
 6229        language: Arc<Language>,
 6230        cx: &mut EditorTestContext,
 6231    ) {
 6232        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6233        cx.set_state(unwrapped_text);
 6234        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6235        cx.assert_editor_state(wrapped_text);
 6236    }
 6237}
 6238
 6239#[gpui::test]
 6240async fn test_rewrap_block_comments(cx: &mut TestAppContext) {
 6241    init_test(cx, |settings| {
 6242        settings.languages.0.extend([(
 6243            "Rust".into(),
 6244            LanguageSettingsContent {
 6245                allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6246                preferred_line_length: Some(40),
 6247                ..Default::default()
 6248            },
 6249        )])
 6250    });
 6251
 6252    let mut cx = EditorTestContext::new(cx).await;
 6253
 6254    let rust_lang = Arc::new(
 6255        Language::new(
 6256            LanguageConfig {
 6257                name: "Rust".into(),
 6258                line_comments: vec!["// ".into()],
 6259                block_comment: Some(BlockCommentConfig {
 6260                    start: "/*".into(),
 6261                    end: "*/".into(),
 6262                    prefix: "* ".into(),
 6263                    tab_size: 1,
 6264                }),
 6265                documentation_comment: Some(BlockCommentConfig {
 6266                    start: "/**".into(),
 6267                    end: "*/".into(),
 6268                    prefix: "* ".into(),
 6269                    tab_size: 1,
 6270                }),
 6271
 6272                ..LanguageConfig::default()
 6273            },
 6274            Some(tree_sitter_rust::LANGUAGE.into()),
 6275        )
 6276        .with_override_query("[(line_comment) (block_comment)] @comment.inclusive")
 6277        .unwrap(),
 6278    );
 6279
 6280    // regular block comment
 6281    assert_rewrap(
 6282        indoc! {"
 6283            /*
 6284             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6285             */
 6286            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6287        "},
 6288        indoc! {"
 6289            /*
 6290             *ˇ Lorem ipsum dolor sit amet,
 6291             * consectetur adipiscing elit.
 6292             */
 6293            /*
 6294             *ˇ Lorem ipsum dolor sit amet,
 6295             * consectetur adipiscing elit.
 6296             */
 6297        "},
 6298        rust_lang.clone(),
 6299        &mut cx,
 6300    );
 6301
 6302    // indent is respected
 6303    assert_rewrap(
 6304        indoc! {"
 6305            {}
 6306                /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6307        "},
 6308        indoc! {"
 6309            {}
 6310                /*
 6311                 *ˇ Lorem ipsum dolor sit amet,
 6312                 * consectetur adipiscing elit.
 6313                 */
 6314        "},
 6315        rust_lang.clone(),
 6316        &mut cx,
 6317    );
 6318
 6319    // short block comments with inline delimiters
 6320    assert_rewrap(
 6321        indoc! {"
 6322            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6323            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6324             */
 6325            /*
 6326             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6327        "},
 6328        indoc! {"
 6329            /*
 6330             *ˇ Lorem ipsum dolor sit amet,
 6331             * consectetur adipiscing elit.
 6332             */
 6333            /*
 6334             *ˇ Lorem ipsum dolor sit amet,
 6335             * consectetur adipiscing elit.
 6336             */
 6337            /*
 6338             *ˇ Lorem ipsum dolor sit amet,
 6339             * consectetur adipiscing elit.
 6340             */
 6341        "},
 6342        rust_lang.clone(),
 6343        &mut cx,
 6344    );
 6345
 6346    // multiline block comment with inline start/end delimiters
 6347    assert_rewrap(
 6348        indoc! {"
 6349            /*ˇ Lorem ipsum dolor sit amet,
 6350             * consectetur adipiscing elit. */
 6351        "},
 6352        indoc! {"
 6353            /*
 6354             *ˇ Lorem ipsum dolor sit amet,
 6355             * consectetur adipiscing elit.
 6356             */
 6357        "},
 6358        rust_lang.clone(),
 6359        &mut cx,
 6360    );
 6361
 6362    // block comment rewrap still respects paragraph bounds
 6363    assert_rewrap(
 6364        indoc! {"
 6365            /*
 6366             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6367             *
 6368             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6369             */
 6370        "},
 6371        indoc! {"
 6372            /*
 6373             *ˇ Lorem ipsum dolor sit amet,
 6374             * consectetur adipiscing elit.
 6375             *
 6376             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6377             */
 6378        "},
 6379        rust_lang.clone(),
 6380        &mut cx,
 6381    );
 6382
 6383    // documentation comments
 6384    assert_rewrap(
 6385        indoc! {"
 6386            /**ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6387            /**
 6388             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6389             */
 6390        "},
 6391        indoc! {"
 6392            /**
 6393             *ˇ Lorem ipsum dolor sit amet,
 6394             * consectetur adipiscing elit.
 6395             */
 6396            /**
 6397             *ˇ Lorem ipsum dolor sit amet,
 6398             * consectetur adipiscing elit.
 6399             */
 6400        "},
 6401        rust_lang.clone(),
 6402        &mut cx,
 6403    );
 6404
 6405    // different, adjacent comments
 6406    assert_rewrap(
 6407        indoc! {"
 6408            /**
 6409             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6410             */
 6411            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6412            //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6413        "},
 6414        indoc! {"
 6415            /**
 6416             *ˇ Lorem ipsum dolor sit amet,
 6417             * consectetur adipiscing elit.
 6418             */
 6419            /*
 6420             *ˇ Lorem ipsum dolor sit amet,
 6421             * consectetur adipiscing elit.
 6422             */
 6423            //ˇ Lorem ipsum dolor sit amet,
 6424            // consectetur adipiscing elit.
 6425        "},
 6426        rust_lang.clone(),
 6427        &mut cx,
 6428    );
 6429
 6430    // selection w/ single short block comment
 6431    assert_rewrap(
 6432        indoc! {"
 6433            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6434        "},
 6435        indoc! {"
 6436            «/*
 6437             * Lorem ipsum dolor sit amet,
 6438             * consectetur adipiscing elit.
 6439             */ˇ»
 6440        "},
 6441        rust_lang.clone(),
 6442        &mut cx,
 6443    );
 6444
 6445    // rewrapping a single comment w/ abutting comments
 6446    assert_rewrap(
 6447        indoc! {"
 6448            /* ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6449            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6450        "},
 6451        indoc! {"
 6452            /*
 6453             * ˇLorem ipsum dolor sit amet,
 6454             * consectetur adipiscing elit.
 6455             */
 6456            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6457        "},
 6458        rust_lang.clone(),
 6459        &mut cx,
 6460    );
 6461
 6462    // selection w/ non-abutting short block comments
 6463    assert_rewrap(
 6464        indoc! {"
 6465            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6466
 6467            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6468        "},
 6469        indoc! {"
 6470            «/*
 6471             * Lorem ipsum dolor sit amet,
 6472             * consectetur adipiscing elit.
 6473             */
 6474
 6475            /*
 6476             * Lorem ipsum dolor sit amet,
 6477             * consectetur adipiscing elit.
 6478             */ˇ»
 6479        "},
 6480        rust_lang.clone(),
 6481        &mut cx,
 6482    );
 6483
 6484    // selection of multiline block comments
 6485    assert_rewrap(
 6486        indoc! {"
 6487            «/* Lorem ipsum dolor sit amet,
 6488             * consectetur adipiscing elit. */ˇ»
 6489        "},
 6490        indoc! {"
 6491            «/*
 6492             * Lorem ipsum dolor sit amet,
 6493             * consectetur adipiscing elit.
 6494             */ˇ»
 6495        "},
 6496        rust_lang.clone(),
 6497        &mut cx,
 6498    );
 6499
 6500    // partial selection of multiline block comments
 6501    assert_rewrap(
 6502        indoc! {"
 6503            «/* Lorem ipsum dolor sit amet,ˇ»
 6504             * consectetur adipiscing elit. */
 6505            /* Lorem ipsum dolor sit amet,
 6506             «* consectetur adipiscing elit. */ˇ»
 6507        "},
 6508        indoc! {"
 6509            «/*
 6510             * Lorem ipsum dolor sit amet,ˇ»
 6511             * consectetur adipiscing elit. */
 6512            /* Lorem ipsum dolor sit amet,
 6513             «* consectetur adipiscing elit.
 6514             */ˇ»
 6515        "},
 6516        rust_lang.clone(),
 6517        &mut cx,
 6518    );
 6519
 6520    // selection w/ abutting short block comments
 6521    // TODO: should not be combined; should rewrap as 2 comments
 6522    assert_rewrap(
 6523        indoc! {"
 6524            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6525            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6526        "},
 6527        // desired behavior:
 6528        // indoc! {"
 6529        //     «/*
 6530        //      * Lorem ipsum dolor sit amet,
 6531        //      * consectetur adipiscing elit.
 6532        //      */
 6533        //     /*
 6534        //      * Lorem ipsum dolor sit amet,
 6535        //      * consectetur adipiscing elit.
 6536        //      */ˇ»
 6537        // "},
 6538        // actual behaviour:
 6539        indoc! {"
 6540            «/*
 6541             * Lorem ipsum dolor sit amet,
 6542             * consectetur adipiscing elit. Lorem
 6543             * ipsum dolor sit amet, consectetur
 6544             * adipiscing elit.
 6545             */ˇ»
 6546        "},
 6547        rust_lang.clone(),
 6548        &mut cx,
 6549    );
 6550
 6551    // TODO: same as above, but with delimiters on separate line
 6552    // assert_rewrap(
 6553    //     indoc! {"
 6554    //         «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6555    //          */
 6556    //         /*
 6557    //          * Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6558    //     "},
 6559    //     // desired:
 6560    //     // indoc! {"
 6561    //     //     «/*
 6562    //     //      * Lorem ipsum dolor sit amet,
 6563    //     //      * consectetur adipiscing elit.
 6564    //     //      */
 6565    //     //     /*
 6566    //     //      * Lorem ipsum dolor sit amet,
 6567    //     //      * consectetur adipiscing elit.
 6568    //     //      */ˇ»
 6569    //     // "},
 6570    //     // actual: (but with trailing w/s on the empty lines)
 6571    //     indoc! {"
 6572    //         «/*
 6573    //          * Lorem ipsum dolor sit amet,
 6574    //          * consectetur adipiscing elit.
 6575    //          *
 6576    //          */
 6577    //         /*
 6578    //          *
 6579    //          * Lorem ipsum dolor sit amet,
 6580    //          * consectetur adipiscing elit.
 6581    //          */ˇ»
 6582    //     "},
 6583    //     rust_lang.clone(),
 6584    //     &mut cx,
 6585    // );
 6586
 6587    // TODO these are unhandled edge cases; not correct, just documenting known issues
 6588    assert_rewrap(
 6589        indoc! {"
 6590            /*
 6591             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6592             */
 6593            /*
 6594             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6595            /*ˇ Lorem ipsum dolor sit amet */ /* consectetur adipiscing elit. */
 6596        "},
 6597        // desired:
 6598        // indoc! {"
 6599        //     /*
 6600        //      *ˇ Lorem ipsum dolor sit amet,
 6601        //      * consectetur adipiscing elit.
 6602        //      */
 6603        //     /*
 6604        //      *ˇ Lorem ipsum dolor sit amet,
 6605        //      * consectetur adipiscing elit.
 6606        //      */
 6607        //     /*
 6608        //      *ˇ Lorem ipsum dolor sit amet
 6609        //      */ /* consectetur adipiscing elit. */
 6610        // "},
 6611        // actual:
 6612        indoc! {"
 6613            /*
 6614             //ˇ Lorem ipsum dolor sit amet,
 6615             // consectetur adipiscing elit.
 6616             */
 6617            /*
 6618             * //ˇ Lorem ipsum dolor sit amet,
 6619             * consectetur adipiscing elit.
 6620             */
 6621            /*
 6622             *ˇ Lorem ipsum dolor sit amet */ /*
 6623             * consectetur adipiscing elit.
 6624             */
 6625        "},
 6626        rust_lang,
 6627        &mut cx,
 6628    );
 6629
 6630    #[track_caller]
 6631    fn assert_rewrap(
 6632        unwrapped_text: &str,
 6633        wrapped_text: &str,
 6634        language: Arc<Language>,
 6635        cx: &mut EditorTestContext,
 6636    ) {
 6637        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6638        cx.set_state(unwrapped_text);
 6639        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6640        cx.assert_editor_state(wrapped_text);
 6641    }
 6642}
 6643
 6644#[gpui::test]
 6645async fn test_hard_wrap(cx: &mut TestAppContext) {
 6646    init_test(cx, |_| {});
 6647    let mut cx = EditorTestContext::new(cx).await;
 6648
 6649    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 6650    cx.update_editor(|editor, _, cx| {
 6651        editor.set_hard_wrap(Some(14), cx);
 6652    });
 6653
 6654    cx.set_state(indoc!(
 6655        "
 6656        one two three ˇ
 6657        "
 6658    ));
 6659    cx.simulate_input("four");
 6660    cx.run_until_parked();
 6661
 6662    cx.assert_editor_state(indoc!(
 6663        "
 6664        one two three
 6665        fourˇ
 6666        "
 6667    ));
 6668
 6669    cx.update_editor(|editor, window, cx| {
 6670        editor.newline(&Default::default(), window, cx);
 6671    });
 6672    cx.run_until_parked();
 6673    cx.assert_editor_state(indoc!(
 6674        "
 6675        one two three
 6676        four
 6677        ˇ
 6678        "
 6679    ));
 6680
 6681    cx.simulate_input("five");
 6682    cx.run_until_parked();
 6683    cx.assert_editor_state(indoc!(
 6684        "
 6685        one two three
 6686        four
 6687        fiveˇ
 6688        "
 6689    ));
 6690
 6691    cx.update_editor(|editor, window, cx| {
 6692        editor.newline(&Default::default(), window, cx);
 6693    });
 6694    cx.run_until_parked();
 6695    cx.simulate_input("# ");
 6696    cx.run_until_parked();
 6697    cx.assert_editor_state(indoc!(
 6698        "
 6699        one two three
 6700        four
 6701        five
 6702        # ˇ
 6703        "
 6704    ));
 6705
 6706    cx.update_editor(|editor, window, cx| {
 6707        editor.newline(&Default::default(), window, cx);
 6708    });
 6709    cx.run_until_parked();
 6710    cx.assert_editor_state(indoc!(
 6711        "
 6712        one two three
 6713        four
 6714        five
 6715        #\x20
 6716 6717        "
 6718    ));
 6719
 6720    cx.simulate_input(" 6");
 6721    cx.run_until_parked();
 6722    cx.assert_editor_state(indoc!(
 6723        "
 6724        one two three
 6725        four
 6726        five
 6727        #
 6728        # 6ˇ
 6729        "
 6730    ));
 6731}
 6732
 6733#[gpui::test]
 6734async fn test_clipboard(cx: &mut TestAppContext) {
 6735    init_test(cx, |_| {});
 6736
 6737    let mut cx = EditorTestContext::new(cx).await;
 6738
 6739    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 6740    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6741    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 6742
 6743    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 6744    cx.set_state("two ˇfour ˇsix ˇ");
 6745    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6746    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 6747
 6748    // Paste again but with only two cursors. Since the number of cursors doesn't
 6749    // match the number of slices in the clipboard, the entire clipboard text
 6750    // is pasted at each cursor.
 6751    cx.set_state("ˇtwo one✅ four three six five ˇ");
 6752    cx.update_editor(|e, window, cx| {
 6753        e.handle_input("( ", window, cx);
 6754        e.paste(&Paste, window, cx);
 6755        e.handle_input(") ", window, cx);
 6756    });
 6757    cx.assert_editor_state(
 6758        &([
 6759            "( one✅ ",
 6760            "three ",
 6761            "five ) ˇtwo one✅ four three six five ( one✅ ",
 6762            "three ",
 6763            "five ) ˇ",
 6764        ]
 6765        .join("\n")),
 6766    );
 6767
 6768    // Cut with three selections, one of which is full-line.
 6769    cx.set_state(indoc! {"
 6770        1«2ˇ»3
 6771        4ˇ567
 6772        «8ˇ»9"});
 6773    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6774    cx.assert_editor_state(indoc! {"
 6775        1ˇ3
 6776        ˇ9"});
 6777
 6778    // Paste with three selections, noticing how the copied selection that was full-line
 6779    // gets inserted before the second cursor.
 6780    cx.set_state(indoc! {"
 6781        1ˇ3
 6782 6783        «oˇ»ne"});
 6784    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6785    cx.assert_editor_state(indoc! {"
 6786        12ˇ3
 6787        4567
 6788 6789        8ˇne"});
 6790
 6791    // Copy with a single cursor only, which writes the whole line into the clipboard.
 6792    cx.set_state(indoc! {"
 6793        The quick brown
 6794        fox juˇmps over
 6795        the lazy dog"});
 6796    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6797    assert_eq!(
 6798        cx.read_from_clipboard()
 6799            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6800        Some("fox jumps over\n".to_string())
 6801    );
 6802
 6803    // Paste with three selections, noticing how the copied full-line selection is inserted
 6804    // before the empty selections but replaces the selection that is non-empty.
 6805    cx.set_state(indoc! {"
 6806        Tˇhe quick brown
 6807        «foˇ»x jumps over
 6808        tˇhe lazy dog"});
 6809    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6810    cx.assert_editor_state(indoc! {"
 6811        fox jumps over
 6812        Tˇhe quick brown
 6813        fox jumps over
 6814        ˇx jumps over
 6815        fox jumps over
 6816        tˇhe lazy dog"});
 6817}
 6818
 6819#[gpui::test]
 6820async fn test_copy_trim(cx: &mut TestAppContext) {
 6821    init_test(cx, |_| {});
 6822
 6823    let mut cx = EditorTestContext::new(cx).await;
 6824    cx.set_state(
 6825        r#"            «for selection in selections.iter() {
 6826            let mut start = selection.start;
 6827            let mut end = selection.end;
 6828            let is_entire_line = selection.is_empty();
 6829            if is_entire_line {
 6830                start = Point::new(start.row, 0);ˇ»
 6831                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6832            }
 6833        "#,
 6834    );
 6835    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6836    assert_eq!(
 6837        cx.read_from_clipboard()
 6838            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6839        Some(
 6840            "for selection in selections.iter() {
 6841            let mut start = selection.start;
 6842            let mut end = selection.end;
 6843            let is_entire_line = selection.is_empty();
 6844            if is_entire_line {
 6845                start = Point::new(start.row, 0);"
 6846                .to_string()
 6847        ),
 6848        "Regular copying preserves all indentation selected",
 6849    );
 6850    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6851    assert_eq!(
 6852        cx.read_from_clipboard()
 6853            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6854        Some(
 6855            "for selection in selections.iter() {
 6856let mut start = selection.start;
 6857let mut end = selection.end;
 6858let is_entire_line = selection.is_empty();
 6859if is_entire_line {
 6860    start = Point::new(start.row, 0);"
 6861                .to_string()
 6862        ),
 6863        "Copying with stripping should strip all leading whitespaces"
 6864    );
 6865
 6866    cx.set_state(
 6867        r#"       «     for selection in selections.iter() {
 6868            let mut start = selection.start;
 6869            let mut end = selection.end;
 6870            let is_entire_line = selection.is_empty();
 6871            if is_entire_line {
 6872                start = Point::new(start.row, 0);ˇ»
 6873                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6874            }
 6875        "#,
 6876    );
 6877    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6878    assert_eq!(
 6879        cx.read_from_clipboard()
 6880            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6881        Some(
 6882            "     for selection in selections.iter() {
 6883            let mut start = selection.start;
 6884            let mut end = selection.end;
 6885            let is_entire_line = selection.is_empty();
 6886            if is_entire_line {
 6887                start = Point::new(start.row, 0);"
 6888                .to_string()
 6889        ),
 6890        "Regular copying preserves all indentation selected",
 6891    );
 6892    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6893    assert_eq!(
 6894        cx.read_from_clipboard()
 6895            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6896        Some(
 6897            "for selection in selections.iter() {
 6898let mut start = selection.start;
 6899let mut end = selection.end;
 6900let is_entire_line = selection.is_empty();
 6901if is_entire_line {
 6902    start = Point::new(start.row, 0);"
 6903                .to_string()
 6904        ),
 6905        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 6906    );
 6907
 6908    cx.set_state(
 6909        r#"       «ˇ     for selection in selections.iter() {
 6910            let mut start = selection.start;
 6911            let mut end = selection.end;
 6912            let is_entire_line = selection.is_empty();
 6913            if is_entire_line {
 6914                start = Point::new(start.row, 0);»
 6915                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6916            }
 6917        "#,
 6918    );
 6919    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6920    assert_eq!(
 6921        cx.read_from_clipboard()
 6922            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6923        Some(
 6924            "     for selection in selections.iter() {
 6925            let mut start = selection.start;
 6926            let mut end = selection.end;
 6927            let is_entire_line = selection.is_empty();
 6928            if is_entire_line {
 6929                start = Point::new(start.row, 0);"
 6930                .to_string()
 6931        ),
 6932        "Regular copying for reverse selection works the same",
 6933    );
 6934    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6935    assert_eq!(
 6936        cx.read_from_clipboard()
 6937            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6938        Some(
 6939            "for selection in selections.iter() {
 6940let mut start = selection.start;
 6941let mut end = selection.end;
 6942let is_entire_line = selection.is_empty();
 6943if is_entire_line {
 6944    start = Point::new(start.row, 0);"
 6945                .to_string()
 6946        ),
 6947        "Copying with stripping for reverse selection works the same"
 6948    );
 6949
 6950    cx.set_state(
 6951        r#"            for selection «in selections.iter() {
 6952            let mut start = selection.start;
 6953            let mut end = selection.end;
 6954            let is_entire_line = selection.is_empty();
 6955            if is_entire_line {
 6956                start = Point::new(start.row, 0);ˇ»
 6957                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6958            }
 6959        "#,
 6960    );
 6961    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6962    assert_eq!(
 6963        cx.read_from_clipboard()
 6964            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6965        Some(
 6966            "in selections.iter() {
 6967            let mut start = selection.start;
 6968            let mut end = selection.end;
 6969            let is_entire_line = selection.is_empty();
 6970            if is_entire_line {
 6971                start = Point::new(start.row, 0);"
 6972                .to_string()
 6973        ),
 6974        "When selecting past the indent, the copying works as usual",
 6975    );
 6976    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6977    assert_eq!(
 6978        cx.read_from_clipboard()
 6979            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6980        Some(
 6981            "in selections.iter() {
 6982            let mut start = selection.start;
 6983            let mut end = selection.end;
 6984            let is_entire_line = selection.is_empty();
 6985            if is_entire_line {
 6986                start = Point::new(start.row, 0);"
 6987                .to_string()
 6988        ),
 6989        "When selecting past the indent, nothing is trimmed"
 6990    );
 6991
 6992    cx.set_state(
 6993        r#"            «for selection in selections.iter() {
 6994            let mut start = selection.start;
 6995
 6996            let mut end = selection.end;
 6997            let is_entire_line = selection.is_empty();
 6998            if is_entire_line {
 6999                start = Point::new(start.row, 0);
 7000ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7001            }
 7002        "#,
 7003    );
 7004    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7005    assert_eq!(
 7006        cx.read_from_clipboard()
 7007            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7008        Some(
 7009            "for selection in selections.iter() {
 7010let mut start = selection.start;
 7011
 7012let mut end = selection.end;
 7013let is_entire_line = selection.is_empty();
 7014if is_entire_line {
 7015    start = Point::new(start.row, 0);
 7016"
 7017            .to_string()
 7018        ),
 7019        "Copying with stripping should ignore empty lines"
 7020    );
 7021}
 7022
 7023#[gpui::test]
 7024async fn test_paste_multiline(cx: &mut TestAppContext) {
 7025    init_test(cx, |_| {});
 7026
 7027    let mut cx = EditorTestContext::new(cx).await;
 7028    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7029
 7030    // Cut an indented block, without the leading whitespace.
 7031    cx.set_state(indoc! {"
 7032        const a: B = (
 7033            c(),
 7034            «d(
 7035                e,
 7036                f
 7037            )ˇ»
 7038        );
 7039    "});
 7040    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7041    cx.assert_editor_state(indoc! {"
 7042        const a: B = (
 7043            c(),
 7044            ˇ
 7045        );
 7046    "});
 7047
 7048    // Paste it at the same position.
 7049    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7050    cx.assert_editor_state(indoc! {"
 7051        const a: B = (
 7052            c(),
 7053            d(
 7054                e,
 7055                f
 7056 7057        );
 7058    "});
 7059
 7060    // Paste it at a line with a lower indent level.
 7061    cx.set_state(indoc! {"
 7062        ˇ
 7063        const a: B = (
 7064            c(),
 7065        );
 7066    "});
 7067    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7068    cx.assert_editor_state(indoc! {"
 7069        d(
 7070            e,
 7071            f
 7072 7073        const a: B = (
 7074            c(),
 7075        );
 7076    "});
 7077
 7078    // Cut an indented block, with the leading whitespace.
 7079    cx.set_state(indoc! {"
 7080        const a: B = (
 7081            c(),
 7082        «    d(
 7083                e,
 7084                f
 7085            )
 7086        ˇ»);
 7087    "});
 7088    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7089    cx.assert_editor_state(indoc! {"
 7090        const a: B = (
 7091            c(),
 7092        ˇ);
 7093    "});
 7094
 7095    // Paste it at the same position.
 7096    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7097    cx.assert_editor_state(indoc! {"
 7098        const a: B = (
 7099            c(),
 7100            d(
 7101                e,
 7102                f
 7103            )
 7104        ˇ);
 7105    "});
 7106
 7107    // Paste it at a line with a higher indent level.
 7108    cx.set_state(indoc! {"
 7109        const a: B = (
 7110            c(),
 7111            d(
 7112                e,
 7113 7114            )
 7115        );
 7116    "});
 7117    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7118    cx.assert_editor_state(indoc! {"
 7119        const a: B = (
 7120            c(),
 7121            d(
 7122                e,
 7123                f    d(
 7124                    e,
 7125                    f
 7126                )
 7127        ˇ
 7128            )
 7129        );
 7130    "});
 7131
 7132    // Copy an indented block, starting mid-line
 7133    cx.set_state(indoc! {"
 7134        const a: B = (
 7135            c(),
 7136            somethin«g(
 7137                e,
 7138                f
 7139            )ˇ»
 7140        );
 7141    "});
 7142    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7143
 7144    // Paste it on a line with a lower indent level
 7145    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 7146    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7147    cx.assert_editor_state(indoc! {"
 7148        const a: B = (
 7149            c(),
 7150            something(
 7151                e,
 7152                f
 7153            )
 7154        );
 7155        g(
 7156            e,
 7157            f
 7158"});
 7159}
 7160
 7161#[gpui::test]
 7162async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 7163    init_test(cx, |_| {});
 7164
 7165    cx.write_to_clipboard(ClipboardItem::new_string(
 7166        "    d(\n        e\n    );\n".into(),
 7167    ));
 7168
 7169    let mut cx = EditorTestContext::new(cx).await;
 7170    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7171
 7172    cx.set_state(indoc! {"
 7173        fn a() {
 7174            b();
 7175            if c() {
 7176                ˇ
 7177            }
 7178        }
 7179    "});
 7180
 7181    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7182    cx.assert_editor_state(indoc! {"
 7183        fn a() {
 7184            b();
 7185            if c() {
 7186                d(
 7187                    e
 7188                );
 7189        ˇ
 7190            }
 7191        }
 7192    "});
 7193
 7194    cx.set_state(indoc! {"
 7195        fn a() {
 7196            b();
 7197            ˇ
 7198        }
 7199    "});
 7200
 7201    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7202    cx.assert_editor_state(indoc! {"
 7203        fn a() {
 7204            b();
 7205            d(
 7206                e
 7207            );
 7208        ˇ
 7209        }
 7210    "});
 7211}
 7212
 7213#[gpui::test]
 7214fn test_select_all(cx: &mut TestAppContext) {
 7215    init_test(cx, |_| {});
 7216
 7217    let editor = cx.add_window(|window, cx| {
 7218        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 7219        build_editor(buffer, window, cx)
 7220    });
 7221    _ = editor.update(cx, |editor, window, cx| {
 7222        editor.select_all(&SelectAll, window, cx);
 7223        assert_eq!(
 7224            editor.selections.display_ranges(cx),
 7225            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 7226        );
 7227    });
 7228}
 7229
 7230#[gpui::test]
 7231fn test_select_line(cx: &mut TestAppContext) {
 7232    init_test(cx, |_| {});
 7233
 7234    let editor = cx.add_window(|window, cx| {
 7235        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 7236        build_editor(buffer, window, cx)
 7237    });
 7238    _ = editor.update(cx, |editor, window, cx| {
 7239        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7240            s.select_display_ranges([
 7241                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7242                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7243                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7244                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 7245            ])
 7246        });
 7247        editor.select_line(&SelectLine, window, cx);
 7248        assert_eq!(
 7249            editor.selections.display_ranges(cx),
 7250            vec![
 7251                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 7252                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 7253            ]
 7254        );
 7255    });
 7256
 7257    _ = editor.update(cx, |editor, window, cx| {
 7258        editor.select_line(&SelectLine, window, cx);
 7259        assert_eq!(
 7260            editor.selections.display_ranges(cx),
 7261            vec![
 7262                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 7263                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 7264            ]
 7265        );
 7266    });
 7267
 7268    _ = editor.update(cx, |editor, window, cx| {
 7269        editor.select_line(&SelectLine, window, cx);
 7270        assert_eq!(
 7271            editor.selections.display_ranges(cx),
 7272            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 7273        );
 7274    });
 7275}
 7276
 7277#[gpui::test]
 7278async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 7279    init_test(cx, |_| {});
 7280    let mut cx = EditorTestContext::new(cx).await;
 7281
 7282    #[track_caller]
 7283    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 7284        cx.set_state(initial_state);
 7285        cx.update_editor(|e, window, cx| {
 7286            e.split_selection_into_lines(&Default::default(), window, cx)
 7287        });
 7288        cx.assert_editor_state(expected_state);
 7289    }
 7290
 7291    // Selection starts and ends at the middle of lines, left-to-right
 7292    test(
 7293        &mut cx,
 7294        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 7295        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7296    );
 7297    // Same thing, right-to-left
 7298    test(
 7299        &mut cx,
 7300        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 7301        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7302    );
 7303
 7304    // Whole buffer, left-to-right, last line *doesn't* end with newline
 7305    test(
 7306        &mut cx,
 7307        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 7308        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7309    );
 7310    // Same thing, right-to-left
 7311    test(
 7312        &mut cx,
 7313        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 7314        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7315    );
 7316
 7317    // Whole buffer, left-to-right, last line ends with newline
 7318    test(
 7319        &mut cx,
 7320        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 7321        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7322    );
 7323    // Same thing, right-to-left
 7324    test(
 7325        &mut cx,
 7326        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 7327        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7328    );
 7329
 7330    // Starts at the end of a line, ends at the start of another
 7331    test(
 7332        &mut cx,
 7333        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 7334        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 7335    );
 7336}
 7337
 7338#[gpui::test]
 7339async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 7340    init_test(cx, |_| {});
 7341
 7342    let editor = cx.add_window(|window, cx| {
 7343        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 7344        build_editor(buffer, window, cx)
 7345    });
 7346
 7347    // setup
 7348    _ = editor.update(cx, |editor, window, cx| {
 7349        editor.fold_creases(
 7350            vec![
 7351                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 7352                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 7353                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 7354            ],
 7355            true,
 7356            window,
 7357            cx,
 7358        );
 7359        assert_eq!(
 7360            editor.display_text(cx),
 7361            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7362        );
 7363    });
 7364
 7365    _ = editor.update(cx, |editor, window, cx| {
 7366        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7367            s.select_display_ranges([
 7368                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7369                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7370                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7371                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 7372            ])
 7373        });
 7374        editor.split_selection_into_lines(&Default::default(), window, cx);
 7375        assert_eq!(
 7376            editor.display_text(cx),
 7377            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7378        );
 7379    });
 7380    EditorTestContext::for_editor(editor, cx)
 7381        .await
 7382        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 7383
 7384    _ = editor.update(cx, |editor, window, cx| {
 7385        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7386            s.select_display_ranges([
 7387                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 7388            ])
 7389        });
 7390        editor.split_selection_into_lines(&Default::default(), window, cx);
 7391        assert_eq!(
 7392            editor.display_text(cx),
 7393            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 7394        );
 7395        assert_eq!(
 7396            editor.selections.display_ranges(cx),
 7397            [
 7398                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 7399                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 7400                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 7401                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 7402                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 7403                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 7404                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 7405            ]
 7406        );
 7407    });
 7408    EditorTestContext::for_editor(editor, cx)
 7409        .await
 7410        .assert_editor_state(
 7411            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 7412        );
 7413}
 7414
 7415#[gpui::test]
 7416async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 7417    init_test(cx, |_| {});
 7418
 7419    let mut cx = EditorTestContext::new(cx).await;
 7420
 7421    cx.set_state(indoc!(
 7422        r#"abc
 7423           defˇghi
 7424
 7425           jk
 7426           nlmo
 7427           "#
 7428    ));
 7429
 7430    cx.update_editor(|editor, window, cx| {
 7431        editor.add_selection_above(&Default::default(), window, cx);
 7432    });
 7433
 7434    cx.assert_editor_state(indoc!(
 7435        r#"abcˇ
 7436           defˇghi
 7437
 7438           jk
 7439           nlmo
 7440           "#
 7441    ));
 7442
 7443    cx.update_editor(|editor, window, cx| {
 7444        editor.add_selection_above(&Default::default(), window, cx);
 7445    });
 7446
 7447    cx.assert_editor_state(indoc!(
 7448        r#"abcˇ
 7449            defˇghi
 7450
 7451            jk
 7452            nlmo
 7453            "#
 7454    ));
 7455
 7456    cx.update_editor(|editor, window, cx| {
 7457        editor.add_selection_below(&Default::default(), window, cx);
 7458    });
 7459
 7460    cx.assert_editor_state(indoc!(
 7461        r#"abc
 7462           defˇghi
 7463
 7464           jk
 7465           nlmo
 7466           "#
 7467    ));
 7468
 7469    cx.update_editor(|editor, window, cx| {
 7470        editor.undo_selection(&Default::default(), window, cx);
 7471    });
 7472
 7473    cx.assert_editor_state(indoc!(
 7474        r#"abcˇ
 7475           defˇghi
 7476
 7477           jk
 7478           nlmo
 7479           "#
 7480    ));
 7481
 7482    cx.update_editor(|editor, window, cx| {
 7483        editor.redo_selection(&Default::default(), window, cx);
 7484    });
 7485
 7486    cx.assert_editor_state(indoc!(
 7487        r#"abc
 7488           defˇghi
 7489
 7490           jk
 7491           nlmo
 7492           "#
 7493    ));
 7494
 7495    cx.update_editor(|editor, window, cx| {
 7496        editor.add_selection_below(&Default::default(), window, cx);
 7497    });
 7498
 7499    cx.assert_editor_state(indoc!(
 7500        r#"abc
 7501           defˇghi
 7502           ˇ
 7503           jk
 7504           nlmo
 7505           "#
 7506    ));
 7507
 7508    cx.update_editor(|editor, window, cx| {
 7509        editor.add_selection_below(&Default::default(), window, cx);
 7510    });
 7511
 7512    cx.assert_editor_state(indoc!(
 7513        r#"abc
 7514           defˇghi
 7515           ˇ
 7516           jkˇ
 7517           nlmo
 7518           "#
 7519    ));
 7520
 7521    cx.update_editor(|editor, window, cx| {
 7522        editor.add_selection_below(&Default::default(), window, cx);
 7523    });
 7524
 7525    cx.assert_editor_state(indoc!(
 7526        r#"abc
 7527           defˇghi
 7528           ˇ
 7529           jkˇ
 7530           nlmˇo
 7531           "#
 7532    ));
 7533
 7534    cx.update_editor(|editor, window, cx| {
 7535        editor.add_selection_below(&Default::default(), window, cx);
 7536    });
 7537
 7538    cx.assert_editor_state(indoc!(
 7539        r#"abc
 7540           defˇghi
 7541           ˇ
 7542           jkˇ
 7543           nlmˇo
 7544           ˇ"#
 7545    ));
 7546
 7547    // change selections
 7548    cx.set_state(indoc!(
 7549        r#"abc
 7550           def«ˇg»hi
 7551
 7552           jk
 7553           nlmo
 7554           "#
 7555    ));
 7556
 7557    cx.update_editor(|editor, window, cx| {
 7558        editor.add_selection_below(&Default::default(), window, cx);
 7559    });
 7560
 7561    cx.assert_editor_state(indoc!(
 7562        r#"abc
 7563           def«ˇg»hi
 7564
 7565           jk
 7566           nlm«ˇo»
 7567           "#
 7568    ));
 7569
 7570    cx.update_editor(|editor, window, cx| {
 7571        editor.add_selection_below(&Default::default(), window, cx);
 7572    });
 7573
 7574    cx.assert_editor_state(indoc!(
 7575        r#"abc
 7576           def«ˇg»hi
 7577
 7578           jk
 7579           nlm«ˇo»
 7580           "#
 7581    ));
 7582
 7583    cx.update_editor(|editor, window, cx| {
 7584        editor.add_selection_above(&Default::default(), window, cx);
 7585    });
 7586
 7587    cx.assert_editor_state(indoc!(
 7588        r#"abc
 7589           def«ˇg»hi
 7590
 7591           jk
 7592           nlmo
 7593           "#
 7594    ));
 7595
 7596    cx.update_editor(|editor, window, cx| {
 7597        editor.add_selection_above(&Default::default(), window, cx);
 7598    });
 7599
 7600    cx.assert_editor_state(indoc!(
 7601        r#"abc
 7602           def«ˇg»hi
 7603
 7604           jk
 7605           nlmo
 7606           "#
 7607    ));
 7608
 7609    // Change selections again
 7610    cx.set_state(indoc!(
 7611        r#"a«bc
 7612           defgˇ»hi
 7613
 7614           jk
 7615           nlmo
 7616           "#
 7617    ));
 7618
 7619    cx.update_editor(|editor, window, cx| {
 7620        editor.add_selection_below(&Default::default(), window, cx);
 7621    });
 7622
 7623    cx.assert_editor_state(indoc!(
 7624        r#"a«bcˇ»
 7625           d«efgˇ»hi
 7626
 7627           j«kˇ»
 7628           nlmo
 7629           "#
 7630    ));
 7631
 7632    cx.update_editor(|editor, window, cx| {
 7633        editor.add_selection_below(&Default::default(), window, cx);
 7634    });
 7635    cx.assert_editor_state(indoc!(
 7636        r#"a«bcˇ»
 7637           d«efgˇ»hi
 7638
 7639           j«kˇ»
 7640           n«lmoˇ»
 7641           "#
 7642    ));
 7643    cx.update_editor(|editor, window, cx| {
 7644        editor.add_selection_above(&Default::default(), window, cx);
 7645    });
 7646
 7647    cx.assert_editor_state(indoc!(
 7648        r#"a«bcˇ»
 7649           d«efgˇ»hi
 7650
 7651           j«kˇ»
 7652           nlmo
 7653           "#
 7654    ));
 7655
 7656    // Change selections again
 7657    cx.set_state(indoc!(
 7658        r#"abc
 7659           d«ˇefghi
 7660
 7661           jk
 7662           nlm»o
 7663           "#
 7664    ));
 7665
 7666    cx.update_editor(|editor, window, cx| {
 7667        editor.add_selection_above(&Default::default(), window, cx);
 7668    });
 7669
 7670    cx.assert_editor_state(indoc!(
 7671        r#"a«ˇbc»
 7672           d«ˇef»ghi
 7673
 7674           j«ˇk»
 7675           n«ˇlm»o
 7676           "#
 7677    ));
 7678
 7679    cx.update_editor(|editor, window, cx| {
 7680        editor.add_selection_below(&Default::default(), window, cx);
 7681    });
 7682
 7683    cx.assert_editor_state(indoc!(
 7684        r#"abc
 7685           d«ˇef»ghi
 7686
 7687           j«ˇk»
 7688           n«ˇlm»o
 7689           "#
 7690    ));
 7691}
 7692
 7693#[gpui::test]
 7694async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 7695    init_test(cx, |_| {});
 7696    let mut cx = EditorTestContext::new(cx).await;
 7697
 7698    cx.set_state(indoc!(
 7699        r#"line onˇe
 7700           liˇne two
 7701           line three
 7702           line four"#
 7703    ));
 7704
 7705    cx.update_editor(|editor, window, cx| {
 7706        editor.add_selection_below(&Default::default(), window, cx);
 7707    });
 7708
 7709    // test multiple cursors expand in the same direction
 7710    cx.assert_editor_state(indoc!(
 7711        r#"line onˇe
 7712           liˇne twˇo
 7713           liˇne three
 7714           line four"#
 7715    ));
 7716
 7717    cx.update_editor(|editor, window, cx| {
 7718        editor.add_selection_below(&Default::default(), window, cx);
 7719    });
 7720
 7721    cx.update_editor(|editor, window, cx| {
 7722        editor.add_selection_below(&Default::default(), window, cx);
 7723    });
 7724
 7725    // test multiple cursors expand below overflow
 7726    cx.assert_editor_state(indoc!(
 7727        r#"line onˇe
 7728           liˇne twˇo
 7729           liˇne thˇree
 7730           liˇne foˇur"#
 7731    ));
 7732
 7733    cx.update_editor(|editor, window, cx| {
 7734        editor.add_selection_above(&Default::default(), window, cx);
 7735    });
 7736
 7737    // test multiple cursors retrieves back correctly
 7738    cx.assert_editor_state(indoc!(
 7739        r#"line onˇe
 7740           liˇne twˇo
 7741           liˇne thˇree
 7742           line four"#
 7743    ));
 7744
 7745    cx.update_editor(|editor, window, cx| {
 7746        editor.add_selection_above(&Default::default(), window, cx);
 7747    });
 7748
 7749    cx.update_editor(|editor, window, cx| {
 7750        editor.add_selection_above(&Default::default(), window, cx);
 7751    });
 7752
 7753    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 7754    cx.assert_editor_state(indoc!(
 7755        r#"liˇne onˇe
 7756           liˇne two
 7757           line three
 7758           line four"#
 7759    ));
 7760
 7761    cx.update_editor(|editor, window, cx| {
 7762        editor.undo_selection(&Default::default(), window, cx);
 7763    });
 7764
 7765    // test undo
 7766    cx.assert_editor_state(indoc!(
 7767        r#"line onˇe
 7768           liˇne twˇo
 7769           line three
 7770           line four"#
 7771    ));
 7772
 7773    cx.update_editor(|editor, window, cx| {
 7774        editor.redo_selection(&Default::default(), window, cx);
 7775    });
 7776
 7777    // test redo
 7778    cx.assert_editor_state(indoc!(
 7779        r#"liˇne onˇe
 7780           liˇne two
 7781           line three
 7782           line four"#
 7783    ));
 7784
 7785    cx.set_state(indoc!(
 7786        r#"abcd
 7787           ef«ghˇ»
 7788           ijkl
 7789           «mˇ»nop"#
 7790    ));
 7791
 7792    cx.update_editor(|editor, window, cx| {
 7793        editor.add_selection_above(&Default::default(), window, cx);
 7794    });
 7795
 7796    // test multiple selections expand in the same direction
 7797    cx.assert_editor_state(indoc!(
 7798        r#"ab«cdˇ»
 7799           ef«ghˇ»
 7800           «iˇ»jkl
 7801           «mˇ»nop"#
 7802    ));
 7803
 7804    cx.update_editor(|editor, window, cx| {
 7805        editor.add_selection_above(&Default::default(), window, cx);
 7806    });
 7807
 7808    // test multiple selection upward overflow
 7809    cx.assert_editor_state(indoc!(
 7810        r#"ab«cdˇ»
 7811           «eˇ»f«ghˇ»
 7812           «iˇ»jkl
 7813           «mˇ»nop"#
 7814    ));
 7815
 7816    cx.update_editor(|editor, window, cx| {
 7817        editor.add_selection_below(&Default::default(), window, cx);
 7818    });
 7819
 7820    // test multiple selection retrieves back correctly
 7821    cx.assert_editor_state(indoc!(
 7822        r#"abcd
 7823           ef«ghˇ»
 7824           «iˇ»jkl
 7825           «mˇ»nop"#
 7826    ));
 7827
 7828    cx.update_editor(|editor, window, cx| {
 7829        editor.add_selection_below(&Default::default(), window, cx);
 7830    });
 7831
 7832    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 7833    cx.assert_editor_state(indoc!(
 7834        r#"abcd
 7835           ef«ghˇ»
 7836           ij«klˇ»
 7837           «mˇ»nop"#
 7838    ));
 7839
 7840    cx.update_editor(|editor, window, cx| {
 7841        editor.undo_selection(&Default::default(), window, cx);
 7842    });
 7843
 7844    // test undo
 7845    cx.assert_editor_state(indoc!(
 7846        r#"abcd
 7847           ef«ghˇ»
 7848           «iˇ»jkl
 7849           «mˇ»nop"#
 7850    ));
 7851
 7852    cx.update_editor(|editor, window, cx| {
 7853        editor.redo_selection(&Default::default(), window, cx);
 7854    });
 7855
 7856    // test redo
 7857    cx.assert_editor_state(indoc!(
 7858        r#"abcd
 7859           ef«ghˇ»
 7860           ij«klˇ»
 7861           «mˇ»nop"#
 7862    ));
 7863}
 7864
 7865#[gpui::test]
 7866async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 7867    init_test(cx, |_| {});
 7868    let mut cx = EditorTestContext::new(cx).await;
 7869
 7870    cx.set_state(indoc!(
 7871        r#"line onˇe
 7872           liˇne two
 7873           line three
 7874           line four"#
 7875    ));
 7876
 7877    cx.update_editor(|editor, window, cx| {
 7878        editor.add_selection_below(&Default::default(), window, cx);
 7879        editor.add_selection_below(&Default::default(), window, cx);
 7880        editor.add_selection_below(&Default::default(), window, cx);
 7881    });
 7882
 7883    // initial state with two multi cursor groups
 7884    cx.assert_editor_state(indoc!(
 7885        r#"line onˇe
 7886           liˇne twˇo
 7887           liˇne thˇree
 7888           liˇne foˇur"#
 7889    ));
 7890
 7891    // add single cursor in middle - simulate opt click
 7892    cx.update_editor(|editor, window, cx| {
 7893        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 7894        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 7895        editor.end_selection(window, cx);
 7896    });
 7897
 7898    cx.assert_editor_state(indoc!(
 7899        r#"line onˇe
 7900           liˇne twˇo
 7901           liˇneˇ thˇree
 7902           liˇne foˇur"#
 7903    ));
 7904
 7905    cx.update_editor(|editor, window, cx| {
 7906        editor.add_selection_above(&Default::default(), window, cx);
 7907    });
 7908
 7909    // test new added selection expands above and existing selection shrinks
 7910    cx.assert_editor_state(indoc!(
 7911        r#"line onˇe
 7912           liˇneˇ twˇo
 7913           liˇneˇ thˇree
 7914           line four"#
 7915    ));
 7916
 7917    cx.update_editor(|editor, window, cx| {
 7918        editor.add_selection_above(&Default::default(), window, cx);
 7919    });
 7920
 7921    // test new added selection expands above and existing selection shrinks
 7922    cx.assert_editor_state(indoc!(
 7923        r#"lineˇ onˇe
 7924           liˇneˇ twˇo
 7925           lineˇ three
 7926           line four"#
 7927    ));
 7928
 7929    // intial state with two selection groups
 7930    cx.set_state(indoc!(
 7931        r#"abcd
 7932           ef«ghˇ»
 7933           ijkl
 7934           «mˇ»nop"#
 7935    ));
 7936
 7937    cx.update_editor(|editor, window, cx| {
 7938        editor.add_selection_above(&Default::default(), window, cx);
 7939        editor.add_selection_above(&Default::default(), window, cx);
 7940    });
 7941
 7942    cx.assert_editor_state(indoc!(
 7943        r#"ab«cdˇ»
 7944           «eˇ»f«ghˇ»
 7945           «iˇ»jkl
 7946           «mˇ»nop"#
 7947    ));
 7948
 7949    // add single selection in middle - simulate opt drag
 7950    cx.update_editor(|editor, window, cx| {
 7951        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 7952        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 7953        editor.update_selection(
 7954            DisplayPoint::new(DisplayRow(2), 4),
 7955            0,
 7956            gpui::Point::<f32>::default(),
 7957            window,
 7958            cx,
 7959        );
 7960        editor.end_selection(window, cx);
 7961    });
 7962
 7963    cx.assert_editor_state(indoc!(
 7964        r#"ab«cdˇ»
 7965           «eˇ»f«ghˇ»
 7966           «iˇ»jk«lˇ»
 7967           «mˇ»nop"#
 7968    ));
 7969
 7970    cx.update_editor(|editor, window, cx| {
 7971        editor.add_selection_below(&Default::default(), window, cx);
 7972    });
 7973
 7974    // test new added selection expands below, others shrinks from above
 7975    cx.assert_editor_state(indoc!(
 7976        r#"abcd
 7977           ef«ghˇ»
 7978           «iˇ»jk«lˇ»
 7979           «mˇ»no«pˇ»"#
 7980    ));
 7981}
 7982
 7983#[gpui::test]
 7984async fn test_select_next(cx: &mut TestAppContext) {
 7985    init_test(cx, |_| {});
 7986
 7987    let mut cx = EditorTestContext::new(cx).await;
 7988    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7989
 7990    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7991        .unwrap();
 7992    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7993
 7994    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7995        .unwrap();
 7996    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 7997
 7998    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7999    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8000
 8001    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8002    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8003
 8004    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8005        .unwrap();
 8006    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8007
 8008    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8009        .unwrap();
 8010    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8011
 8012    // Test selection direction should be preserved
 8013    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8014
 8015    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8016        .unwrap();
 8017    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 8018}
 8019
 8020#[gpui::test]
 8021async fn test_select_all_matches(cx: &mut TestAppContext) {
 8022    init_test(cx, |_| {});
 8023
 8024    let mut cx = EditorTestContext::new(cx).await;
 8025
 8026    // Test caret-only selections
 8027    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8028    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8029        .unwrap();
 8030    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8031
 8032    // Test left-to-right selections
 8033    cx.set_state("abc\n«abcˇ»\nabc");
 8034    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8035        .unwrap();
 8036    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 8037
 8038    // Test right-to-left selections
 8039    cx.set_state("abc\n«ˇabc»\nabc");
 8040    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8041        .unwrap();
 8042    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 8043
 8044    // Test selecting whitespace with caret selection
 8045    cx.set_state("abc\nˇ   abc\nabc");
 8046    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8047        .unwrap();
 8048    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 8049
 8050    // Test selecting whitespace with left-to-right selection
 8051    cx.set_state("abc\n«ˇ  »abc\nabc");
 8052    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8053        .unwrap();
 8054    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 8055
 8056    // Test no matches with right-to-left selection
 8057    cx.set_state("abc\n«  ˇ»abc\nabc");
 8058    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8059        .unwrap();
 8060    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 8061
 8062    // Test with a single word and clip_at_line_ends=true (#29823)
 8063    cx.set_state("aˇbc");
 8064    cx.update_editor(|e, window, cx| {
 8065        e.set_clip_at_line_ends(true, cx);
 8066        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8067        e.set_clip_at_line_ends(false, cx);
 8068    });
 8069    cx.assert_editor_state("«abcˇ»");
 8070}
 8071
 8072#[gpui::test]
 8073async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 8074    init_test(cx, |_| {});
 8075
 8076    let mut cx = EditorTestContext::new(cx).await;
 8077
 8078    let large_body_1 = "\nd".repeat(200);
 8079    let large_body_2 = "\ne".repeat(200);
 8080
 8081    cx.set_state(&format!(
 8082        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 8083    ));
 8084    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 8085        let scroll_position = editor.scroll_position(cx);
 8086        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 8087        scroll_position
 8088    });
 8089
 8090    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8091        .unwrap();
 8092    cx.assert_editor_state(&format!(
 8093        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 8094    ));
 8095    let scroll_position_after_selection =
 8096        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 8097    assert_eq!(
 8098        initial_scroll_position, scroll_position_after_selection,
 8099        "Scroll position should not change after selecting all matches"
 8100    );
 8101}
 8102
 8103#[gpui::test]
 8104async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 8105    init_test(cx, |_| {});
 8106
 8107    let mut cx = EditorLspTestContext::new_rust(
 8108        lsp::ServerCapabilities {
 8109            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8110            ..Default::default()
 8111        },
 8112        cx,
 8113    )
 8114    .await;
 8115
 8116    cx.set_state(indoc! {"
 8117        line 1
 8118        line 2
 8119        linˇe 3
 8120        line 4
 8121        line 5
 8122    "});
 8123
 8124    // Make an edit
 8125    cx.update_editor(|editor, window, cx| {
 8126        editor.handle_input("X", window, cx);
 8127    });
 8128
 8129    // Move cursor to a different position
 8130    cx.update_editor(|editor, window, cx| {
 8131        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8132            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 8133        });
 8134    });
 8135
 8136    cx.assert_editor_state(indoc! {"
 8137        line 1
 8138        line 2
 8139        linXe 3
 8140        line 4
 8141        liˇne 5
 8142    "});
 8143
 8144    cx.lsp
 8145        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 8146            Ok(Some(vec![lsp::TextEdit::new(
 8147                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 8148                "PREFIX ".to_string(),
 8149            )]))
 8150        });
 8151
 8152    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 8153        .unwrap()
 8154        .await
 8155        .unwrap();
 8156
 8157    cx.assert_editor_state(indoc! {"
 8158        PREFIX line 1
 8159        line 2
 8160        linXe 3
 8161        line 4
 8162        liˇne 5
 8163    "});
 8164
 8165    // Undo formatting
 8166    cx.update_editor(|editor, window, cx| {
 8167        editor.undo(&Default::default(), window, cx);
 8168    });
 8169
 8170    // Verify cursor moved back to position after edit
 8171    cx.assert_editor_state(indoc! {"
 8172        line 1
 8173        line 2
 8174        linXˇe 3
 8175        line 4
 8176        line 5
 8177    "});
 8178}
 8179
 8180#[gpui::test]
 8181async fn test_undo_edit_prediction_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 8182    init_test(cx, |_| {});
 8183
 8184    let mut cx = EditorTestContext::new(cx).await;
 8185
 8186    let provider = cx.new(|_| FakeEditPredictionProvider::default());
 8187    cx.update_editor(|editor, window, cx| {
 8188        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 8189    });
 8190
 8191    cx.set_state(indoc! {"
 8192        line 1
 8193        line 2
 8194        linˇe 3
 8195        line 4
 8196        line 5
 8197        line 6
 8198        line 7
 8199        line 8
 8200        line 9
 8201        line 10
 8202    "});
 8203
 8204    let snapshot = cx.buffer_snapshot();
 8205    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 8206
 8207    cx.update(|_, cx| {
 8208        provider.update(cx, |provider, _| {
 8209            provider.set_edit_prediction(Some(edit_prediction::EditPrediction {
 8210                id: None,
 8211                edits: vec![(edit_position..edit_position, "X".into())],
 8212                edit_preview: None,
 8213            }))
 8214        })
 8215    });
 8216
 8217    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
 8218    cx.update_editor(|editor, window, cx| {
 8219        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 8220    });
 8221
 8222    cx.assert_editor_state(indoc! {"
 8223        line 1
 8224        line 2
 8225        lineXˇ 3
 8226        line 4
 8227        line 5
 8228        line 6
 8229        line 7
 8230        line 8
 8231        line 9
 8232        line 10
 8233    "});
 8234
 8235    cx.update_editor(|editor, window, cx| {
 8236        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8237            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 8238        });
 8239    });
 8240
 8241    cx.assert_editor_state(indoc! {"
 8242        line 1
 8243        line 2
 8244        lineX 3
 8245        line 4
 8246        line 5
 8247        line 6
 8248        line 7
 8249        line 8
 8250        line 9
 8251        liˇne 10
 8252    "});
 8253
 8254    cx.update_editor(|editor, window, cx| {
 8255        editor.undo(&Default::default(), window, cx);
 8256    });
 8257
 8258    cx.assert_editor_state(indoc! {"
 8259        line 1
 8260        line 2
 8261        lineˇ 3
 8262        line 4
 8263        line 5
 8264        line 6
 8265        line 7
 8266        line 8
 8267        line 9
 8268        line 10
 8269    "});
 8270}
 8271
 8272#[gpui::test]
 8273async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 8274    init_test(cx, |_| {});
 8275
 8276    let mut cx = EditorTestContext::new(cx).await;
 8277    cx.set_state(
 8278        r#"let foo = 2;
 8279lˇet foo = 2;
 8280let fooˇ = 2;
 8281let foo = 2;
 8282let foo = ˇ2;"#,
 8283    );
 8284
 8285    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8286        .unwrap();
 8287    cx.assert_editor_state(
 8288        r#"let foo = 2;
 8289«letˇ» foo = 2;
 8290let «fooˇ» = 2;
 8291let foo = 2;
 8292let foo = «2ˇ»;"#,
 8293    );
 8294
 8295    // noop for multiple selections with different contents
 8296    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8297        .unwrap();
 8298    cx.assert_editor_state(
 8299        r#"let foo = 2;
 8300«letˇ» foo = 2;
 8301let «fooˇ» = 2;
 8302let foo = 2;
 8303let foo = «2ˇ»;"#,
 8304    );
 8305
 8306    // Test last selection direction should be preserved
 8307    cx.set_state(
 8308        r#"let foo = 2;
 8309let foo = 2;
 8310let «fooˇ» = 2;
 8311let «ˇfoo» = 2;
 8312let foo = 2;"#,
 8313    );
 8314
 8315    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8316        .unwrap();
 8317    cx.assert_editor_state(
 8318        r#"let foo = 2;
 8319let foo = 2;
 8320let «fooˇ» = 2;
 8321let «ˇfoo» = 2;
 8322let «ˇfoo» = 2;"#,
 8323    );
 8324}
 8325
 8326#[gpui::test]
 8327async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 8328    init_test(cx, |_| {});
 8329
 8330    let mut cx =
 8331        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 8332
 8333    cx.assert_editor_state(indoc! {"
 8334        ˇbbb
 8335        ccc
 8336
 8337        bbb
 8338        ccc
 8339        "});
 8340    cx.dispatch_action(SelectPrevious::default());
 8341    cx.assert_editor_state(indoc! {"
 8342                «bbbˇ»
 8343                ccc
 8344
 8345                bbb
 8346                ccc
 8347                "});
 8348    cx.dispatch_action(SelectPrevious::default());
 8349    cx.assert_editor_state(indoc! {"
 8350                «bbbˇ»
 8351                ccc
 8352
 8353                «bbbˇ»
 8354                ccc
 8355                "});
 8356}
 8357
 8358#[gpui::test]
 8359async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 8360    init_test(cx, |_| {});
 8361
 8362    let mut cx = EditorTestContext::new(cx).await;
 8363    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8364
 8365    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8366        .unwrap();
 8367    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8368
 8369    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8370        .unwrap();
 8371    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8372
 8373    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8374    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8375
 8376    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8377    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8378
 8379    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8380        .unwrap();
 8381    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 8382
 8383    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8384        .unwrap();
 8385    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8386}
 8387
 8388#[gpui::test]
 8389async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 8390    init_test(cx, |_| {});
 8391
 8392    let mut cx = EditorTestContext::new(cx).await;
 8393    cx.set_state("");
 8394
 8395    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8396        .unwrap();
 8397    cx.assert_editor_state("«aˇ»");
 8398    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8399        .unwrap();
 8400    cx.assert_editor_state("«aˇ»");
 8401}
 8402
 8403#[gpui::test]
 8404async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 8405    init_test(cx, |_| {});
 8406
 8407    let mut cx = EditorTestContext::new(cx).await;
 8408    cx.set_state(
 8409        r#"let foo = 2;
 8410lˇet foo = 2;
 8411let fooˇ = 2;
 8412let foo = 2;
 8413let foo = ˇ2;"#,
 8414    );
 8415
 8416    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8417        .unwrap();
 8418    cx.assert_editor_state(
 8419        r#"let foo = 2;
 8420«letˇ» foo = 2;
 8421let «fooˇ» = 2;
 8422let foo = 2;
 8423let foo = «2ˇ»;"#,
 8424    );
 8425
 8426    // noop for multiple selections with different contents
 8427    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8428        .unwrap();
 8429    cx.assert_editor_state(
 8430        r#"let foo = 2;
 8431«letˇ» foo = 2;
 8432let «fooˇ» = 2;
 8433let foo = 2;
 8434let foo = «2ˇ»;"#,
 8435    );
 8436}
 8437
 8438#[gpui::test]
 8439async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 8440    init_test(cx, |_| {});
 8441
 8442    let mut cx = EditorTestContext::new(cx).await;
 8443    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8444
 8445    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8446        .unwrap();
 8447    // selection direction is preserved
 8448    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 8449
 8450    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8451        .unwrap();
 8452    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 8453
 8454    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8455    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 8456
 8457    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8458    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 8459
 8460    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8461        .unwrap();
 8462    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 8463
 8464    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8465        .unwrap();
 8466    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 8467}
 8468
 8469#[gpui::test]
 8470async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 8471    init_test(cx, |_| {});
 8472
 8473    let language = Arc::new(Language::new(
 8474        LanguageConfig::default(),
 8475        Some(tree_sitter_rust::LANGUAGE.into()),
 8476    ));
 8477
 8478    let text = r#"
 8479        use mod1::mod2::{mod3, mod4};
 8480
 8481        fn fn_1(param1: bool, param2: &str) {
 8482            let var1 = "text";
 8483        }
 8484    "#
 8485    .unindent();
 8486
 8487    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8488    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8489    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8490
 8491    editor
 8492        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8493        .await;
 8494
 8495    editor.update_in(cx, |editor, window, cx| {
 8496        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8497            s.select_display_ranges([
 8498                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 8499                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 8500                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 8501            ]);
 8502        });
 8503        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8504    });
 8505    editor.update(cx, |editor, cx| {
 8506        assert_text_with_selections(
 8507            editor,
 8508            indoc! {r#"
 8509                use mod1::mod2::{mod3, «mod4ˇ»};
 8510
 8511                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8512                    let var1 = "«ˇtext»";
 8513                }
 8514            "#},
 8515            cx,
 8516        );
 8517    });
 8518
 8519    editor.update_in(cx, |editor, window, cx| {
 8520        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8521    });
 8522    editor.update(cx, |editor, cx| {
 8523        assert_text_with_selections(
 8524            editor,
 8525            indoc! {r#"
 8526                use mod1::mod2::«{mod3, mod4}ˇ»;
 8527
 8528                «ˇfn fn_1(param1: bool, param2: &str) {
 8529                    let var1 = "text";
 8530 8531            "#},
 8532            cx,
 8533        );
 8534    });
 8535
 8536    editor.update_in(cx, |editor, window, cx| {
 8537        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8538    });
 8539    assert_eq!(
 8540        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 8541        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 8542    );
 8543
 8544    // Trying to expand the selected syntax node one more time has no effect.
 8545    editor.update_in(cx, |editor, window, cx| {
 8546        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8547    });
 8548    assert_eq!(
 8549        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 8550        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 8551    );
 8552
 8553    editor.update_in(cx, |editor, window, cx| {
 8554        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8555    });
 8556    editor.update(cx, |editor, cx| {
 8557        assert_text_with_selections(
 8558            editor,
 8559            indoc! {r#"
 8560                use mod1::mod2::«{mod3, mod4}ˇ»;
 8561
 8562                «ˇfn fn_1(param1: bool, param2: &str) {
 8563                    let var1 = "text";
 8564 8565            "#},
 8566            cx,
 8567        );
 8568    });
 8569
 8570    editor.update_in(cx, |editor, window, cx| {
 8571        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8572    });
 8573    editor.update(cx, |editor, cx| {
 8574        assert_text_with_selections(
 8575            editor,
 8576            indoc! {r#"
 8577                use mod1::mod2::{mod3, «mod4ˇ»};
 8578
 8579                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8580                    let var1 = "«ˇtext»";
 8581                }
 8582            "#},
 8583            cx,
 8584        );
 8585    });
 8586
 8587    editor.update_in(cx, |editor, window, cx| {
 8588        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8589    });
 8590    editor.update(cx, |editor, cx| {
 8591        assert_text_with_selections(
 8592            editor,
 8593            indoc! {r#"
 8594                use mod1::mod2::{mod3, mo«ˇ»d4};
 8595
 8596                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 8597                    let var1 = "te«ˇ»xt";
 8598                }
 8599            "#},
 8600            cx,
 8601        );
 8602    });
 8603
 8604    // Trying to shrink the selected syntax node one more time has no effect.
 8605    editor.update_in(cx, |editor, window, cx| {
 8606        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8607    });
 8608    editor.update_in(cx, |editor, _, cx| {
 8609        assert_text_with_selections(
 8610            editor,
 8611            indoc! {r#"
 8612                use mod1::mod2::{mod3, mo«ˇ»d4};
 8613
 8614                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 8615                    let var1 = "te«ˇ»xt";
 8616                }
 8617            "#},
 8618            cx,
 8619        );
 8620    });
 8621
 8622    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 8623    // a fold.
 8624    editor.update_in(cx, |editor, window, cx| {
 8625        editor.fold_creases(
 8626            vec![
 8627                Crease::simple(
 8628                    Point::new(0, 21)..Point::new(0, 24),
 8629                    FoldPlaceholder::test(),
 8630                ),
 8631                Crease::simple(
 8632                    Point::new(3, 20)..Point::new(3, 22),
 8633                    FoldPlaceholder::test(),
 8634                ),
 8635            ],
 8636            true,
 8637            window,
 8638            cx,
 8639        );
 8640        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8641    });
 8642    editor.update(cx, |editor, cx| {
 8643        assert_text_with_selections(
 8644            editor,
 8645            indoc! {r#"
 8646                use mod1::mod2::«{mod3, mod4}ˇ»;
 8647
 8648                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8649                    let var1 = "«ˇtext»";
 8650                }
 8651            "#},
 8652            cx,
 8653        );
 8654    });
 8655}
 8656
 8657#[gpui::test]
 8658async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 8659    init_test(cx, |_| {});
 8660
 8661    let language = Arc::new(Language::new(
 8662        LanguageConfig::default(),
 8663        Some(tree_sitter_rust::LANGUAGE.into()),
 8664    ));
 8665
 8666    let text = "let a = 2;";
 8667
 8668    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8669    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8670    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8671
 8672    editor
 8673        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8674        .await;
 8675
 8676    // Test case 1: Cursor at end of word
 8677    editor.update_in(cx, |editor, window, cx| {
 8678        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8679            s.select_display_ranges([
 8680                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 8681            ]);
 8682        });
 8683    });
 8684    editor.update(cx, |editor, cx| {
 8685        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 8686    });
 8687    editor.update_in(cx, |editor, window, cx| {
 8688        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8689    });
 8690    editor.update(cx, |editor, cx| {
 8691        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 8692    });
 8693    editor.update_in(cx, |editor, window, cx| {
 8694        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8695    });
 8696    editor.update(cx, |editor, cx| {
 8697        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 8698    });
 8699
 8700    // Test case 2: Cursor at end of statement
 8701    editor.update_in(cx, |editor, window, cx| {
 8702        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8703            s.select_display_ranges([
 8704                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 8705            ]);
 8706        });
 8707    });
 8708    editor.update(cx, |editor, cx| {
 8709        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 8710    });
 8711    editor.update_in(cx, |editor, window, cx| {
 8712        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8713    });
 8714    editor.update(cx, |editor, cx| {
 8715        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 8716    });
 8717}
 8718
 8719#[gpui::test]
 8720async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 8721    init_test(cx, |_| {});
 8722
 8723    let language = Arc::new(Language::new(
 8724        LanguageConfig::default(),
 8725        Some(tree_sitter_rust::LANGUAGE.into()),
 8726    ));
 8727
 8728    let text = r#"
 8729        use mod1::mod2::{mod3, mod4};
 8730
 8731        fn fn_1(param1: bool, param2: &str) {
 8732            let var1 = "hello world";
 8733        }
 8734    "#
 8735    .unindent();
 8736
 8737    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8738    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8739    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8740
 8741    editor
 8742        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8743        .await;
 8744
 8745    // Test 1: Cursor on a letter of a string word
 8746    editor.update_in(cx, |editor, window, cx| {
 8747        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8748            s.select_display_ranges([
 8749                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 8750            ]);
 8751        });
 8752    });
 8753    editor.update_in(cx, |editor, window, cx| {
 8754        assert_text_with_selections(
 8755            editor,
 8756            indoc! {r#"
 8757                use mod1::mod2::{mod3, mod4};
 8758
 8759                fn fn_1(param1: bool, param2: &str) {
 8760                    let var1 = "hˇello world";
 8761                }
 8762            "#},
 8763            cx,
 8764        );
 8765        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8766        assert_text_with_selections(
 8767            editor,
 8768            indoc! {r#"
 8769                use mod1::mod2::{mod3, mod4};
 8770
 8771                fn fn_1(param1: bool, param2: &str) {
 8772                    let var1 = "«ˇhello» world";
 8773                }
 8774            "#},
 8775            cx,
 8776        );
 8777    });
 8778
 8779    // Test 2: Partial selection within a word
 8780    editor.update_in(cx, |editor, window, cx| {
 8781        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8782            s.select_display_ranges([
 8783                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 8784            ]);
 8785        });
 8786    });
 8787    editor.update_in(cx, |editor, window, cx| {
 8788        assert_text_with_selections(
 8789            editor,
 8790            indoc! {r#"
 8791                use mod1::mod2::{mod3, mod4};
 8792
 8793                fn fn_1(param1: bool, param2: &str) {
 8794                    let var1 = "h«elˇ»lo world";
 8795                }
 8796            "#},
 8797            cx,
 8798        );
 8799        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8800        assert_text_with_selections(
 8801            editor,
 8802            indoc! {r#"
 8803                use mod1::mod2::{mod3, mod4};
 8804
 8805                fn fn_1(param1: bool, param2: &str) {
 8806                    let var1 = "«ˇhello» world";
 8807                }
 8808            "#},
 8809            cx,
 8810        );
 8811    });
 8812
 8813    // Test 3: Complete word already selected
 8814    editor.update_in(cx, |editor, window, cx| {
 8815        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8816            s.select_display_ranges([
 8817                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 8818            ]);
 8819        });
 8820    });
 8821    editor.update_in(cx, |editor, window, cx| {
 8822        assert_text_with_selections(
 8823            editor,
 8824            indoc! {r#"
 8825                use mod1::mod2::{mod3, mod4};
 8826
 8827                fn fn_1(param1: bool, param2: &str) {
 8828                    let var1 = "«helloˇ» world";
 8829                }
 8830            "#},
 8831            cx,
 8832        );
 8833        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8834        assert_text_with_selections(
 8835            editor,
 8836            indoc! {r#"
 8837                use mod1::mod2::{mod3, mod4};
 8838
 8839                fn fn_1(param1: bool, param2: &str) {
 8840                    let var1 = "«hello worldˇ»";
 8841                }
 8842            "#},
 8843            cx,
 8844        );
 8845    });
 8846
 8847    // Test 4: Selection spanning across words
 8848    editor.update_in(cx, |editor, window, cx| {
 8849        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8850            s.select_display_ranges([
 8851                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 8852            ]);
 8853        });
 8854    });
 8855    editor.update_in(cx, |editor, window, cx| {
 8856        assert_text_with_selections(
 8857            editor,
 8858            indoc! {r#"
 8859                use mod1::mod2::{mod3, mod4};
 8860
 8861                fn fn_1(param1: bool, param2: &str) {
 8862                    let var1 = "hel«lo woˇ»rld";
 8863                }
 8864            "#},
 8865            cx,
 8866        );
 8867        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8868        assert_text_with_selections(
 8869            editor,
 8870            indoc! {r#"
 8871                use mod1::mod2::{mod3, mod4};
 8872
 8873                fn fn_1(param1: bool, param2: &str) {
 8874                    let var1 = "«ˇhello world»";
 8875                }
 8876            "#},
 8877            cx,
 8878        );
 8879    });
 8880
 8881    // Test 5: Expansion beyond string
 8882    editor.update_in(cx, |editor, window, cx| {
 8883        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8884        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8885        assert_text_with_selections(
 8886            editor,
 8887            indoc! {r#"
 8888                use mod1::mod2::{mod3, mod4};
 8889
 8890                fn fn_1(param1: bool, param2: &str) {
 8891                    «ˇlet var1 = "hello world";»
 8892                }
 8893            "#},
 8894            cx,
 8895        );
 8896    });
 8897}
 8898
 8899#[gpui::test]
 8900async fn test_unwrap_syntax_nodes(cx: &mut gpui::TestAppContext) {
 8901    init_test(cx, |_| {});
 8902
 8903    let mut cx = EditorTestContext::new(cx).await;
 8904
 8905    let language = Arc::new(Language::new(
 8906        LanguageConfig::default(),
 8907        Some(tree_sitter_rust::LANGUAGE.into()),
 8908    ));
 8909
 8910    cx.update_buffer(|buffer, cx| {
 8911        buffer.set_language(Some(language), cx);
 8912    });
 8913
 8914    cx.set_state(indoc! { r#"use mod1::{mod2::{«mod3ˇ», mod4}, mod5::{mod6, «mod7ˇ»}};"# });
 8915    cx.update_editor(|editor, window, cx| {
 8916        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 8917    });
 8918
 8919    cx.assert_editor_state(indoc! { r#"use mod1::{mod2::«mod3ˇ», mod5::«mod7ˇ»};"# });
 8920}
 8921
 8922#[gpui::test]
 8923async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 8924    init_test(cx, |_| {});
 8925
 8926    let base_text = r#"
 8927        impl A {
 8928            // this is an uncommitted comment
 8929
 8930            fn b() {
 8931                c();
 8932            }
 8933
 8934            // this is another uncommitted comment
 8935
 8936            fn d() {
 8937                // e
 8938                // f
 8939            }
 8940        }
 8941
 8942        fn g() {
 8943            // h
 8944        }
 8945    "#
 8946    .unindent();
 8947
 8948    let text = r#"
 8949        ˇimpl A {
 8950
 8951            fn b() {
 8952                c();
 8953            }
 8954
 8955            fn d() {
 8956                // e
 8957                // f
 8958            }
 8959        }
 8960
 8961        fn g() {
 8962            // h
 8963        }
 8964    "#
 8965    .unindent();
 8966
 8967    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 8968    cx.set_state(&text);
 8969    cx.set_head_text(&base_text);
 8970    cx.update_editor(|editor, window, cx| {
 8971        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 8972    });
 8973
 8974    cx.assert_state_with_diff(
 8975        "
 8976        ˇimpl A {
 8977      -     // this is an uncommitted comment
 8978
 8979            fn b() {
 8980                c();
 8981            }
 8982
 8983      -     // this is another uncommitted comment
 8984      -
 8985            fn d() {
 8986                // e
 8987                // f
 8988            }
 8989        }
 8990
 8991        fn g() {
 8992            // h
 8993        }
 8994    "
 8995        .unindent(),
 8996    );
 8997
 8998    let expected_display_text = "
 8999        impl A {
 9000            // this is an uncommitted comment
 9001
 9002            fn b() {
 9003 9004            }
 9005
 9006            // this is another uncommitted comment
 9007
 9008            fn d() {
 9009 9010            }
 9011        }
 9012
 9013        fn g() {
 9014 9015        }
 9016        "
 9017    .unindent();
 9018
 9019    cx.update_editor(|editor, window, cx| {
 9020        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 9021        assert_eq!(editor.display_text(cx), expected_display_text);
 9022    });
 9023}
 9024
 9025#[gpui::test]
 9026async fn test_autoindent(cx: &mut TestAppContext) {
 9027    init_test(cx, |_| {});
 9028
 9029    let language = Arc::new(
 9030        Language::new(
 9031            LanguageConfig {
 9032                brackets: BracketPairConfig {
 9033                    pairs: vec![
 9034                        BracketPair {
 9035                            start: "{".to_string(),
 9036                            end: "}".to_string(),
 9037                            close: false,
 9038                            surround: false,
 9039                            newline: true,
 9040                        },
 9041                        BracketPair {
 9042                            start: "(".to_string(),
 9043                            end: ")".to_string(),
 9044                            close: false,
 9045                            surround: false,
 9046                            newline: true,
 9047                        },
 9048                    ],
 9049                    ..Default::default()
 9050                },
 9051                ..Default::default()
 9052            },
 9053            Some(tree_sitter_rust::LANGUAGE.into()),
 9054        )
 9055        .with_indents_query(
 9056            r#"
 9057                (_ "(" ")" @end) @indent
 9058                (_ "{" "}" @end) @indent
 9059            "#,
 9060        )
 9061        .unwrap(),
 9062    );
 9063
 9064    let text = "fn a() {}";
 9065
 9066    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9067    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9068    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9069    editor
 9070        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9071        .await;
 9072
 9073    editor.update_in(cx, |editor, window, cx| {
 9074        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9075            s.select_ranges([5..5, 8..8, 9..9])
 9076        });
 9077        editor.newline(&Newline, window, cx);
 9078        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 9079        assert_eq!(
 9080            editor.selections.ranges(cx),
 9081            &[
 9082                Point::new(1, 4)..Point::new(1, 4),
 9083                Point::new(3, 4)..Point::new(3, 4),
 9084                Point::new(5, 0)..Point::new(5, 0)
 9085            ]
 9086        );
 9087    });
 9088}
 9089
 9090#[gpui::test]
 9091async fn test_autoindent_disabled(cx: &mut TestAppContext) {
 9092    init_test(cx, |settings| settings.defaults.auto_indent = Some(false));
 9093
 9094    let language = Arc::new(
 9095        Language::new(
 9096            LanguageConfig {
 9097                brackets: BracketPairConfig {
 9098                    pairs: vec![
 9099                        BracketPair {
 9100                            start: "{".to_string(),
 9101                            end: "}".to_string(),
 9102                            close: false,
 9103                            surround: false,
 9104                            newline: true,
 9105                        },
 9106                        BracketPair {
 9107                            start: "(".to_string(),
 9108                            end: ")".to_string(),
 9109                            close: false,
 9110                            surround: false,
 9111                            newline: true,
 9112                        },
 9113                    ],
 9114                    ..Default::default()
 9115                },
 9116                ..Default::default()
 9117            },
 9118            Some(tree_sitter_rust::LANGUAGE.into()),
 9119        )
 9120        .with_indents_query(
 9121            r#"
 9122                (_ "(" ")" @end) @indent
 9123                (_ "{" "}" @end) @indent
 9124            "#,
 9125        )
 9126        .unwrap(),
 9127    );
 9128
 9129    let text = "fn a() {}";
 9130
 9131    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9132    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9133    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9134    editor
 9135        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9136        .await;
 9137
 9138    editor.update_in(cx, |editor, window, cx| {
 9139        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9140            s.select_ranges([5..5, 8..8, 9..9])
 9141        });
 9142        editor.newline(&Newline, window, cx);
 9143        assert_eq!(
 9144            editor.text(cx),
 9145            indoc!(
 9146                "
 9147                fn a(
 9148
 9149                ) {
 9150
 9151                }
 9152                "
 9153            )
 9154        );
 9155        assert_eq!(
 9156            editor.selections.ranges(cx),
 9157            &[
 9158                Point::new(1, 0)..Point::new(1, 0),
 9159                Point::new(3, 0)..Point::new(3, 0),
 9160                Point::new(5, 0)..Point::new(5, 0)
 9161            ]
 9162        );
 9163    });
 9164}
 9165
 9166#[gpui::test]
 9167async fn test_autoindent_disabled_with_nested_language(cx: &mut TestAppContext) {
 9168    init_test(cx, |settings| {
 9169        settings.defaults.auto_indent = Some(true);
 9170        settings.languages.0.insert(
 9171            "python".into(),
 9172            LanguageSettingsContent {
 9173                auto_indent: Some(false),
 9174                ..Default::default()
 9175            },
 9176        );
 9177    });
 9178
 9179    let mut cx = EditorTestContext::new(cx).await;
 9180
 9181    let injected_language = Arc::new(
 9182        Language::new(
 9183            LanguageConfig {
 9184                brackets: BracketPairConfig {
 9185                    pairs: vec![
 9186                        BracketPair {
 9187                            start: "{".to_string(),
 9188                            end: "}".to_string(),
 9189                            close: false,
 9190                            surround: false,
 9191                            newline: true,
 9192                        },
 9193                        BracketPair {
 9194                            start: "(".to_string(),
 9195                            end: ")".to_string(),
 9196                            close: true,
 9197                            surround: false,
 9198                            newline: true,
 9199                        },
 9200                    ],
 9201                    ..Default::default()
 9202                },
 9203                name: "python".into(),
 9204                ..Default::default()
 9205            },
 9206            Some(tree_sitter_python::LANGUAGE.into()),
 9207        )
 9208        .with_indents_query(
 9209            r#"
 9210                (_ "(" ")" @end) @indent
 9211                (_ "{" "}" @end) @indent
 9212            "#,
 9213        )
 9214        .unwrap(),
 9215    );
 9216
 9217    let language = Arc::new(
 9218        Language::new(
 9219            LanguageConfig {
 9220                brackets: BracketPairConfig {
 9221                    pairs: vec![
 9222                        BracketPair {
 9223                            start: "{".to_string(),
 9224                            end: "}".to_string(),
 9225                            close: false,
 9226                            surround: false,
 9227                            newline: true,
 9228                        },
 9229                        BracketPair {
 9230                            start: "(".to_string(),
 9231                            end: ")".to_string(),
 9232                            close: true,
 9233                            surround: false,
 9234                            newline: true,
 9235                        },
 9236                    ],
 9237                    ..Default::default()
 9238                },
 9239                name: LanguageName::new("rust"),
 9240                ..Default::default()
 9241            },
 9242            Some(tree_sitter_rust::LANGUAGE.into()),
 9243        )
 9244        .with_indents_query(
 9245            r#"
 9246                (_ "(" ")" @end) @indent
 9247                (_ "{" "}" @end) @indent
 9248            "#,
 9249        )
 9250        .unwrap()
 9251        .with_injection_query(
 9252            r#"
 9253            (macro_invocation
 9254                macro: (identifier) @_macro_name
 9255                (token_tree) @injection.content
 9256                (#set! injection.language "python"))
 9257           "#,
 9258        )
 9259        .unwrap(),
 9260    );
 9261
 9262    cx.language_registry().add(injected_language);
 9263    cx.language_registry().add(language.clone());
 9264
 9265    cx.update_buffer(|buffer, cx| {
 9266        buffer.set_language(Some(language), cx);
 9267    });
 9268
 9269    cx.set_state(r#"struct A {ˇ}"#);
 9270
 9271    cx.update_editor(|editor, window, cx| {
 9272        editor.newline(&Default::default(), window, cx);
 9273    });
 9274
 9275    cx.assert_editor_state(indoc!(
 9276        "struct A {
 9277            ˇ
 9278        }"
 9279    ));
 9280
 9281    cx.set_state(r#"select_biased!(ˇ)"#);
 9282
 9283    cx.update_editor(|editor, window, cx| {
 9284        editor.newline(&Default::default(), window, cx);
 9285        editor.handle_input("def ", window, cx);
 9286        editor.handle_input("(", window, cx);
 9287        editor.newline(&Default::default(), window, cx);
 9288        editor.handle_input("a", window, cx);
 9289    });
 9290
 9291    cx.assert_editor_state(indoc!(
 9292        "select_biased!(
 9293        def (
 9294 9295        )
 9296        )"
 9297    ));
 9298}
 9299
 9300#[gpui::test]
 9301async fn test_autoindent_selections(cx: &mut TestAppContext) {
 9302    init_test(cx, |_| {});
 9303
 9304    {
 9305        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9306        cx.set_state(indoc! {"
 9307            impl A {
 9308
 9309                fn b() {}
 9310
 9311            «fn c() {
 9312
 9313            }ˇ»
 9314            }
 9315        "});
 9316
 9317        cx.update_editor(|editor, window, cx| {
 9318            editor.autoindent(&Default::default(), window, cx);
 9319        });
 9320
 9321        cx.assert_editor_state(indoc! {"
 9322            impl A {
 9323
 9324                fn b() {}
 9325
 9326                «fn c() {
 9327
 9328                }ˇ»
 9329            }
 9330        "});
 9331    }
 9332
 9333    {
 9334        let mut cx = EditorTestContext::new_multibuffer(
 9335            cx,
 9336            [indoc! { "
 9337                impl A {
 9338                «
 9339                // a
 9340                fn b(){}
 9341                »
 9342                «
 9343                    }
 9344                    fn c(){}
 9345                »
 9346            "}],
 9347        );
 9348
 9349        let buffer = cx.update_editor(|editor, _, cx| {
 9350            let buffer = editor.buffer().update(cx, |buffer, _| {
 9351                buffer.all_buffers().iter().next().unwrap().clone()
 9352            });
 9353            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 9354            buffer
 9355        });
 9356
 9357        cx.run_until_parked();
 9358        cx.update_editor(|editor, window, cx| {
 9359            editor.select_all(&Default::default(), window, cx);
 9360            editor.autoindent(&Default::default(), window, cx)
 9361        });
 9362        cx.run_until_parked();
 9363
 9364        cx.update(|_, cx| {
 9365            assert_eq!(
 9366                buffer.read(cx).text(),
 9367                indoc! { "
 9368                    impl A {
 9369
 9370                        // a
 9371                        fn b(){}
 9372
 9373
 9374                    }
 9375                    fn c(){}
 9376
 9377                " }
 9378            )
 9379        });
 9380    }
 9381}
 9382
 9383#[gpui::test]
 9384async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 9385    init_test(cx, |_| {});
 9386
 9387    let mut cx = EditorTestContext::new(cx).await;
 9388
 9389    let language = Arc::new(Language::new(
 9390        LanguageConfig {
 9391            brackets: BracketPairConfig {
 9392                pairs: vec![
 9393                    BracketPair {
 9394                        start: "{".to_string(),
 9395                        end: "}".to_string(),
 9396                        close: true,
 9397                        surround: true,
 9398                        newline: true,
 9399                    },
 9400                    BracketPair {
 9401                        start: "(".to_string(),
 9402                        end: ")".to_string(),
 9403                        close: true,
 9404                        surround: true,
 9405                        newline: true,
 9406                    },
 9407                    BracketPair {
 9408                        start: "/*".to_string(),
 9409                        end: " */".to_string(),
 9410                        close: true,
 9411                        surround: true,
 9412                        newline: true,
 9413                    },
 9414                    BracketPair {
 9415                        start: "[".to_string(),
 9416                        end: "]".to_string(),
 9417                        close: false,
 9418                        surround: false,
 9419                        newline: true,
 9420                    },
 9421                    BracketPair {
 9422                        start: "\"".to_string(),
 9423                        end: "\"".to_string(),
 9424                        close: true,
 9425                        surround: true,
 9426                        newline: false,
 9427                    },
 9428                    BracketPair {
 9429                        start: "<".to_string(),
 9430                        end: ">".to_string(),
 9431                        close: false,
 9432                        surround: true,
 9433                        newline: true,
 9434                    },
 9435                ],
 9436                ..Default::default()
 9437            },
 9438            autoclose_before: "})]".to_string(),
 9439            ..Default::default()
 9440        },
 9441        Some(tree_sitter_rust::LANGUAGE.into()),
 9442    ));
 9443
 9444    cx.language_registry().add(language.clone());
 9445    cx.update_buffer(|buffer, cx| {
 9446        buffer.set_language(Some(language), cx);
 9447    });
 9448
 9449    cx.set_state(
 9450        &r#"
 9451            🏀ˇ
 9452            εˇ
 9453            ❤️ˇ
 9454        "#
 9455        .unindent(),
 9456    );
 9457
 9458    // autoclose multiple nested brackets at multiple cursors
 9459    cx.update_editor(|editor, window, cx| {
 9460        editor.handle_input("{", window, cx);
 9461        editor.handle_input("{", window, cx);
 9462        editor.handle_input("{", window, cx);
 9463    });
 9464    cx.assert_editor_state(
 9465        &"
 9466            🏀{{{ˇ}}}
 9467            ε{{{ˇ}}}
 9468            ❤️{{{ˇ}}}
 9469        "
 9470        .unindent(),
 9471    );
 9472
 9473    // insert a different closing bracket
 9474    cx.update_editor(|editor, window, cx| {
 9475        editor.handle_input(")", window, cx);
 9476    });
 9477    cx.assert_editor_state(
 9478        &"
 9479            🏀{{{)ˇ}}}
 9480            ε{{{)ˇ}}}
 9481            ❤️{{{)ˇ}}}
 9482        "
 9483        .unindent(),
 9484    );
 9485
 9486    // skip over the auto-closed brackets when typing a closing bracket
 9487    cx.update_editor(|editor, window, cx| {
 9488        editor.move_right(&MoveRight, window, cx);
 9489        editor.handle_input("}", window, cx);
 9490        editor.handle_input("}", window, cx);
 9491        editor.handle_input("}", window, cx);
 9492    });
 9493    cx.assert_editor_state(
 9494        &"
 9495            🏀{{{)}}}}ˇ
 9496            ε{{{)}}}}ˇ
 9497            ❤️{{{)}}}}ˇ
 9498        "
 9499        .unindent(),
 9500    );
 9501
 9502    // autoclose multi-character pairs
 9503    cx.set_state(
 9504        &"
 9505            ˇ
 9506            ˇ
 9507        "
 9508        .unindent(),
 9509    );
 9510    cx.update_editor(|editor, window, cx| {
 9511        editor.handle_input("/", window, cx);
 9512        editor.handle_input("*", window, cx);
 9513    });
 9514    cx.assert_editor_state(
 9515        &"
 9516            /*ˇ */
 9517            /*ˇ */
 9518        "
 9519        .unindent(),
 9520    );
 9521
 9522    // one cursor autocloses a multi-character pair, one cursor
 9523    // does not autoclose.
 9524    cx.set_state(
 9525        &"
 9526 9527            ˇ
 9528        "
 9529        .unindent(),
 9530    );
 9531    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
 9532    cx.assert_editor_state(
 9533        &"
 9534            /*ˇ */
 9535 9536        "
 9537        .unindent(),
 9538    );
 9539
 9540    // Don't autoclose if the next character isn't whitespace and isn't
 9541    // listed in the language's "autoclose_before" section.
 9542    cx.set_state("ˇa b");
 9543    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 9544    cx.assert_editor_state("{ˇa b");
 9545
 9546    // Don't autoclose if `close` is false for the bracket pair
 9547    cx.set_state("ˇ");
 9548    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
 9549    cx.assert_editor_state("");
 9550
 9551    // Surround with brackets if text is selected
 9552    cx.set_state("«aˇ» b");
 9553    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 9554    cx.assert_editor_state("{«aˇ»} b");
 9555
 9556    // Autoclose when not immediately after a word character
 9557    cx.set_state("a ˇ");
 9558    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9559    cx.assert_editor_state("a \"ˇ\"");
 9560
 9561    // Autoclose pair where the start and end characters are the same
 9562    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9563    cx.assert_editor_state("a \"\"ˇ");
 9564
 9565    // Don't autoclose when immediately after a word character
 9566    cx.set_state("");
 9567    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9568    cx.assert_editor_state("a\"ˇ");
 9569
 9570    // Do autoclose when after a non-word character
 9571    cx.set_state("");
 9572    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9573    cx.assert_editor_state("{\"ˇ\"");
 9574
 9575    // Non identical pairs autoclose regardless of preceding character
 9576    cx.set_state("");
 9577    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 9578    cx.assert_editor_state("a{ˇ}");
 9579
 9580    // Don't autoclose pair if autoclose is disabled
 9581    cx.set_state("ˇ");
 9582    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 9583    cx.assert_editor_state("");
 9584
 9585    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
 9586    cx.set_state("«aˇ» b");
 9587    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 9588    cx.assert_editor_state("<«aˇ»> b");
 9589}
 9590
 9591#[gpui::test]
 9592async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
 9593    init_test(cx, |settings| {
 9594        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 9595    });
 9596
 9597    let mut cx = EditorTestContext::new(cx).await;
 9598
 9599    let language = Arc::new(Language::new(
 9600        LanguageConfig {
 9601            brackets: BracketPairConfig {
 9602                pairs: vec![
 9603                    BracketPair {
 9604                        start: "{".to_string(),
 9605                        end: "}".to_string(),
 9606                        close: true,
 9607                        surround: true,
 9608                        newline: true,
 9609                    },
 9610                    BracketPair {
 9611                        start: "(".to_string(),
 9612                        end: ")".to_string(),
 9613                        close: true,
 9614                        surround: true,
 9615                        newline: true,
 9616                    },
 9617                    BracketPair {
 9618                        start: "[".to_string(),
 9619                        end: "]".to_string(),
 9620                        close: false,
 9621                        surround: false,
 9622                        newline: true,
 9623                    },
 9624                ],
 9625                ..Default::default()
 9626            },
 9627            autoclose_before: "})]".to_string(),
 9628            ..Default::default()
 9629        },
 9630        Some(tree_sitter_rust::LANGUAGE.into()),
 9631    ));
 9632
 9633    cx.language_registry().add(language.clone());
 9634    cx.update_buffer(|buffer, cx| {
 9635        buffer.set_language(Some(language), cx);
 9636    });
 9637
 9638    cx.set_state(
 9639        &"
 9640            ˇ
 9641            ˇ
 9642            ˇ
 9643        "
 9644        .unindent(),
 9645    );
 9646
 9647    // ensure only matching closing brackets are skipped over
 9648    cx.update_editor(|editor, window, cx| {
 9649        editor.handle_input("}", window, cx);
 9650        editor.move_left(&MoveLeft, window, cx);
 9651        editor.handle_input(")", window, cx);
 9652        editor.move_left(&MoveLeft, window, cx);
 9653    });
 9654    cx.assert_editor_state(
 9655        &"
 9656            ˇ)}
 9657            ˇ)}
 9658            ˇ)}
 9659        "
 9660        .unindent(),
 9661    );
 9662
 9663    // skip-over closing brackets at multiple cursors
 9664    cx.update_editor(|editor, window, cx| {
 9665        editor.handle_input(")", window, cx);
 9666        editor.handle_input("}", window, cx);
 9667    });
 9668    cx.assert_editor_state(
 9669        &"
 9670            )}ˇ
 9671            )}ˇ
 9672            )}ˇ
 9673        "
 9674        .unindent(),
 9675    );
 9676
 9677    // ignore non-close brackets
 9678    cx.update_editor(|editor, window, cx| {
 9679        editor.handle_input("]", window, cx);
 9680        editor.move_left(&MoveLeft, window, cx);
 9681        editor.handle_input("]", window, cx);
 9682    });
 9683    cx.assert_editor_state(
 9684        &"
 9685            )}]ˇ]
 9686            )}]ˇ]
 9687            )}]ˇ]
 9688        "
 9689        .unindent(),
 9690    );
 9691}
 9692
 9693#[gpui::test]
 9694async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
 9695    init_test(cx, |_| {});
 9696
 9697    let mut cx = EditorTestContext::new(cx).await;
 9698
 9699    let html_language = Arc::new(
 9700        Language::new(
 9701            LanguageConfig {
 9702                name: "HTML".into(),
 9703                brackets: BracketPairConfig {
 9704                    pairs: vec![
 9705                        BracketPair {
 9706                            start: "<".into(),
 9707                            end: ">".into(),
 9708                            close: true,
 9709                            ..Default::default()
 9710                        },
 9711                        BracketPair {
 9712                            start: "{".into(),
 9713                            end: "}".into(),
 9714                            close: true,
 9715                            ..Default::default()
 9716                        },
 9717                        BracketPair {
 9718                            start: "(".into(),
 9719                            end: ")".into(),
 9720                            close: true,
 9721                            ..Default::default()
 9722                        },
 9723                    ],
 9724                    ..Default::default()
 9725                },
 9726                autoclose_before: "})]>".into(),
 9727                ..Default::default()
 9728            },
 9729            Some(tree_sitter_html::LANGUAGE.into()),
 9730        )
 9731        .with_injection_query(
 9732            r#"
 9733            (script_element
 9734                (raw_text) @injection.content
 9735                (#set! injection.language "javascript"))
 9736            "#,
 9737        )
 9738        .unwrap(),
 9739    );
 9740
 9741    let javascript_language = Arc::new(Language::new(
 9742        LanguageConfig {
 9743            name: "JavaScript".into(),
 9744            brackets: BracketPairConfig {
 9745                pairs: vec![
 9746                    BracketPair {
 9747                        start: "/*".into(),
 9748                        end: " */".into(),
 9749                        close: true,
 9750                        ..Default::default()
 9751                    },
 9752                    BracketPair {
 9753                        start: "{".into(),
 9754                        end: "}".into(),
 9755                        close: true,
 9756                        ..Default::default()
 9757                    },
 9758                    BracketPair {
 9759                        start: "(".into(),
 9760                        end: ")".into(),
 9761                        close: true,
 9762                        ..Default::default()
 9763                    },
 9764                ],
 9765                ..Default::default()
 9766            },
 9767            autoclose_before: "})]>".into(),
 9768            ..Default::default()
 9769        },
 9770        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 9771    ));
 9772
 9773    cx.language_registry().add(html_language.clone());
 9774    cx.language_registry().add(javascript_language);
 9775    cx.executor().run_until_parked();
 9776
 9777    cx.update_buffer(|buffer, cx| {
 9778        buffer.set_language(Some(html_language), cx);
 9779    });
 9780
 9781    cx.set_state(
 9782        &r#"
 9783            <body>ˇ
 9784                <script>
 9785                    var x = 1;ˇ
 9786                </script>
 9787            </body>ˇ
 9788        "#
 9789        .unindent(),
 9790    );
 9791
 9792    // Precondition: different languages are active at different locations.
 9793    cx.update_editor(|editor, window, cx| {
 9794        let snapshot = editor.snapshot(window, cx);
 9795        let cursors = editor.selections.ranges::<usize>(cx);
 9796        let languages = cursors
 9797            .iter()
 9798            .map(|c| snapshot.language_at(c.start).unwrap().name())
 9799            .collect::<Vec<_>>();
 9800        assert_eq!(
 9801            languages,
 9802            &["HTML".into(), "JavaScript".into(), "HTML".into()]
 9803        );
 9804    });
 9805
 9806    // Angle brackets autoclose in HTML, but not JavaScript.
 9807    cx.update_editor(|editor, window, cx| {
 9808        editor.handle_input("<", window, cx);
 9809        editor.handle_input("a", window, cx);
 9810    });
 9811    cx.assert_editor_state(
 9812        &r#"
 9813            <body><aˇ>
 9814                <script>
 9815                    var x = 1;<aˇ
 9816                </script>
 9817            </body><aˇ>
 9818        "#
 9819        .unindent(),
 9820    );
 9821
 9822    // Curly braces and parens autoclose in both HTML and JavaScript.
 9823    cx.update_editor(|editor, window, cx| {
 9824        editor.handle_input(" b=", window, cx);
 9825        editor.handle_input("{", window, cx);
 9826        editor.handle_input("c", window, cx);
 9827        editor.handle_input("(", window, cx);
 9828    });
 9829    cx.assert_editor_state(
 9830        &r#"
 9831            <body><a b={c(ˇ)}>
 9832                <script>
 9833                    var x = 1;<a b={c(ˇ)}
 9834                </script>
 9835            </body><a b={c(ˇ)}>
 9836        "#
 9837        .unindent(),
 9838    );
 9839
 9840    // Brackets that were already autoclosed are skipped.
 9841    cx.update_editor(|editor, window, cx| {
 9842        editor.handle_input(")", window, cx);
 9843        editor.handle_input("d", window, cx);
 9844        editor.handle_input("}", window, cx);
 9845    });
 9846    cx.assert_editor_state(
 9847        &r#"
 9848            <body><a b={c()d}ˇ>
 9849                <script>
 9850                    var x = 1;<a b={c()d}ˇ
 9851                </script>
 9852            </body><a b={c()d}ˇ>
 9853        "#
 9854        .unindent(),
 9855    );
 9856    cx.update_editor(|editor, window, cx| {
 9857        editor.handle_input(">", window, cx);
 9858    });
 9859    cx.assert_editor_state(
 9860        &r#"
 9861            <body><a b={c()d}>ˇ
 9862                <script>
 9863                    var x = 1;<a b={c()d}>ˇ
 9864                </script>
 9865            </body><a b={c()d}>ˇ
 9866        "#
 9867        .unindent(),
 9868    );
 9869
 9870    // Reset
 9871    cx.set_state(
 9872        &r#"
 9873            <body>ˇ
 9874                <script>
 9875                    var x = 1;ˇ
 9876                </script>
 9877            </body>ˇ
 9878        "#
 9879        .unindent(),
 9880    );
 9881
 9882    cx.update_editor(|editor, window, cx| {
 9883        editor.handle_input("<", window, cx);
 9884    });
 9885    cx.assert_editor_state(
 9886        &r#"
 9887            <body><ˇ>
 9888                <script>
 9889                    var x = 1;<ˇ
 9890                </script>
 9891            </body><ˇ>
 9892        "#
 9893        .unindent(),
 9894    );
 9895
 9896    // When backspacing, the closing angle brackets are removed.
 9897    cx.update_editor(|editor, window, cx| {
 9898        editor.backspace(&Backspace, window, cx);
 9899    });
 9900    cx.assert_editor_state(
 9901        &r#"
 9902            <body>ˇ
 9903                <script>
 9904                    var x = 1;ˇ
 9905                </script>
 9906            </body>ˇ
 9907        "#
 9908        .unindent(),
 9909    );
 9910
 9911    // Block comments autoclose in JavaScript, but not HTML.
 9912    cx.update_editor(|editor, window, cx| {
 9913        editor.handle_input("/", window, cx);
 9914        editor.handle_input("*", window, cx);
 9915    });
 9916    cx.assert_editor_state(
 9917        &r#"
 9918            <body>/*ˇ
 9919                <script>
 9920                    var x = 1;/*ˇ */
 9921                </script>
 9922            </body>/*ˇ
 9923        "#
 9924        .unindent(),
 9925    );
 9926}
 9927
 9928#[gpui::test]
 9929async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
 9930    init_test(cx, |_| {});
 9931
 9932    let mut cx = EditorTestContext::new(cx).await;
 9933
 9934    let rust_language = Arc::new(
 9935        Language::new(
 9936            LanguageConfig {
 9937                name: "Rust".into(),
 9938                brackets: serde_json::from_value(json!([
 9939                    { "start": "{", "end": "}", "close": true, "newline": true },
 9940                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
 9941                ]))
 9942                .unwrap(),
 9943                autoclose_before: "})]>".into(),
 9944                ..Default::default()
 9945            },
 9946            Some(tree_sitter_rust::LANGUAGE.into()),
 9947        )
 9948        .with_override_query("(string_literal) @string")
 9949        .unwrap(),
 9950    );
 9951
 9952    cx.language_registry().add(rust_language.clone());
 9953    cx.update_buffer(|buffer, cx| {
 9954        buffer.set_language(Some(rust_language), cx);
 9955    });
 9956
 9957    cx.set_state(
 9958        &r#"
 9959            let x = ˇ
 9960        "#
 9961        .unindent(),
 9962    );
 9963
 9964    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
 9965    cx.update_editor(|editor, window, cx| {
 9966        editor.handle_input("\"", window, cx);
 9967    });
 9968    cx.assert_editor_state(
 9969        &r#"
 9970            let x = "ˇ"
 9971        "#
 9972        .unindent(),
 9973    );
 9974
 9975    // Inserting another quotation mark. The cursor moves across the existing
 9976    // automatically-inserted quotation mark.
 9977    cx.update_editor(|editor, window, cx| {
 9978        editor.handle_input("\"", window, cx);
 9979    });
 9980    cx.assert_editor_state(
 9981        &r#"
 9982            let x = ""ˇ
 9983        "#
 9984        .unindent(),
 9985    );
 9986
 9987    // Reset
 9988    cx.set_state(
 9989        &r#"
 9990            let x = ˇ
 9991        "#
 9992        .unindent(),
 9993    );
 9994
 9995    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
 9996    cx.update_editor(|editor, window, cx| {
 9997        editor.handle_input("\"", window, cx);
 9998        editor.handle_input(" ", window, cx);
 9999        editor.move_left(&Default::default(), window, cx);
10000        editor.handle_input("\\", window, cx);
10001        editor.handle_input("\"", window, cx);
10002    });
10003    cx.assert_editor_state(
10004        &r#"
10005            let x = "\"ˇ "
10006        "#
10007        .unindent(),
10008    );
10009
10010    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
10011    // mark. Nothing is inserted.
10012    cx.update_editor(|editor, window, cx| {
10013        editor.move_right(&Default::default(), window, cx);
10014        editor.handle_input("\"", window, cx);
10015    });
10016    cx.assert_editor_state(
10017        &r#"
10018            let x = "\" "ˇ
10019        "#
10020        .unindent(),
10021    );
10022}
10023
10024#[gpui::test]
10025async fn test_surround_with_pair(cx: &mut TestAppContext) {
10026    init_test(cx, |_| {});
10027
10028    let language = Arc::new(Language::new(
10029        LanguageConfig {
10030            brackets: BracketPairConfig {
10031                pairs: vec![
10032                    BracketPair {
10033                        start: "{".to_string(),
10034                        end: "}".to_string(),
10035                        close: true,
10036                        surround: true,
10037                        newline: true,
10038                    },
10039                    BracketPair {
10040                        start: "/* ".to_string(),
10041                        end: "*/".to_string(),
10042                        close: true,
10043                        surround: true,
10044                        ..Default::default()
10045                    },
10046                ],
10047                ..Default::default()
10048            },
10049            ..Default::default()
10050        },
10051        Some(tree_sitter_rust::LANGUAGE.into()),
10052    ));
10053
10054    let text = r#"
10055        a
10056        b
10057        c
10058    "#
10059    .unindent();
10060
10061    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10062    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10063    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10064    editor
10065        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10066        .await;
10067
10068    editor.update_in(cx, |editor, window, cx| {
10069        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10070            s.select_display_ranges([
10071                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10072                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10073                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
10074            ])
10075        });
10076
10077        editor.handle_input("{", window, cx);
10078        editor.handle_input("{", window, cx);
10079        editor.handle_input("{", window, cx);
10080        assert_eq!(
10081            editor.text(cx),
10082            "
10083                {{{a}}}
10084                {{{b}}}
10085                {{{c}}}
10086            "
10087            .unindent()
10088        );
10089        assert_eq!(
10090            editor.selections.display_ranges(cx),
10091            [
10092                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
10093                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
10094                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
10095            ]
10096        );
10097
10098        editor.undo(&Undo, window, cx);
10099        editor.undo(&Undo, window, cx);
10100        editor.undo(&Undo, window, cx);
10101        assert_eq!(
10102            editor.text(cx),
10103            "
10104                a
10105                b
10106                c
10107            "
10108            .unindent()
10109        );
10110        assert_eq!(
10111            editor.selections.display_ranges(cx),
10112            [
10113                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10114                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10115                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10116            ]
10117        );
10118
10119        // Ensure inserting the first character of a multi-byte bracket pair
10120        // doesn't surround the selections with the bracket.
10121        editor.handle_input("/", window, cx);
10122        assert_eq!(
10123            editor.text(cx),
10124            "
10125                /
10126                /
10127                /
10128            "
10129            .unindent()
10130        );
10131        assert_eq!(
10132            editor.selections.display_ranges(cx),
10133            [
10134                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10135                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10136                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10137            ]
10138        );
10139
10140        editor.undo(&Undo, window, cx);
10141        assert_eq!(
10142            editor.text(cx),
10143            "
10144                a
10145                b
10146                c
10147            "
10148            .unindent()
10149        );
10150        assert_eq!(
10151            editor.selections.display_ranges(cx),
10152            [
10153                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10154                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10155                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10156            ]
10157        );
10158
10159        // Ensure inserting the last character of a multi-byte bracket pair
10160        // doesn't surround the selections with the bracket.
10161        editor.handle_input("*", window, cx);
10162        assert_eq!(
10163            editor.text(cx),
10164            "
10165                *
10166                *
10167                *
10168            "
10169            .unindent()
10170        );
10171        assert_eq!(
10172            editor.selections.display_ranges(cx),
10173            [
10174                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10175                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10176                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10177            ]
10178        );
10179    });
10180}
10181
10182#[gpui::test]
10183async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
10184    init_test(cx, |_| {});
10185
10186    let language = Arc::new(Language::new(
10187        LanguageConfig {
10188            brackets: BracketPairConfig {
10189                pairs: vec![BracketPair {
10190                    start: "{".to_string(),
10191                    end: "}".to_string(),
10192                    close: true,
10193                    surround: true,
10194                    newline: true,
10195                }],
10196                ..Default::default()
10197            },
10198            autoclose_before: "}".to_string(),
10199            ..Default::default()
10200        },
10201        Some(tree_sitter_rust::LANGUAGE.into()),
10202    ));
10203
10204    let text = r#"
10205        a
10206        b
10207        c
10208    "#
10209    .unindent();
10210
10211    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10212    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10213    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10214    editor
10215        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10216        .await;
10217
10218    editor.update_in(cx, |editor, window, cx| {
10219        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10220            s.select_ranges([
10221                Point::new(0, 1)..Point::new(0, 1),
10222                Point::new(1, 1)..Point::new(1, 1),
10223                Point::new(2, 1)..Point::new(2, 1),
10224            ])
10225        });
10226
10227        editor.handle_input("{", window, cx);
10228        editor.handle_input("{", window, cx);
10229        editor.handle_input("_", window, cx);
10230        assert_eq!(
10231            editor.text(cx),
10232            "
10233                a{{_}}
10234                b{{_}}
10235                c{{_}}
10236            "
10237            .unindent()
10238        );
10239        assert_eq!(
10240            editor.selections.ranges::<Point>(cx),
10241            [
10242                Point::new(0, 4)..Point::new(0, 4),
10243                Point::new(1, 4)..Point::new(1, 4),
10244                Point::new(2, 4)..Point::new(2, 4)
10245            ]
10246        );
10247
10248        editor.backspace(&Default::default(), window, cx);
10249        editor.backspace(&Default::default(), window, cx);
10250        assert_eq!(
10251            editor.text(cx),
10252            "
10253                a{}
10254                b{}
10255                c{}
10256            "
10257            .unindent()
10258        );
10259        assert_eq!(
10260            editor.selections.ranges::<Point>(cx),
10261            [
10262                Point::new(0, 2)..Point::new(0, 2),
10263                Point::new(1, 2)..Point::new(1, 2),
10264                Point::new(2, 2)..Point::new(2, 2)
10265            ]
10266        );
10267
10268        editor.delete_to_previous_word_start(&Default::default(), window, cx);
10269        assert_eq!(
10270            editor.text(cx),
10271            "
10272                a
10273                b
10274                c
10275            "
10276            .unindent()
10277        );
10278        assert_eq!(
10279            editor.selections.ranges::<Point>(cx),
10280            [
10281                Point::new(0, 1)..Point::new(0, 1),
10282                Point::new(1, 1)..Point::new(1, 1),
10283                Point::new(2, 1)..Point::new(2, 1)
10284            ]
10285        );
10286    });
10287}
10288
10289#[gpui::test]
10290async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
10291    init_test(cx, |settings| {
10292        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
10293    });
10294
10295    let mut cx = EditorTestContext::new(cx).await;
10296
10297    let language = Arc::new(Language::new(
10298        LanguageConfig {
10299            brackets: BracketPairConfig {
10300                pairs: vec![
10301                    BracketPair {
10302                        start: "{".to_string(),
10303                        end: "}".to_string(),
10304                        close: true,
10305                        surround: true,
10306                        newline: true,
10307                    },
10308                    BracketPair {
10309                        start: "(".to_string(),
10310                        end: ")".to_string(),
10311                        close: true,
10312                        surround: true,
10313                        newline: true,
10314                    },
10315                    BracketPair {
10316                        start: "[".to_string(),
10317                        end: "]".to_string(),
10318                        close: false,
10319                        surround: true,
10320                        newline: true,
10321                    },
10322                ],
10323                ..Default::default()
10324            },
10325            autoclose_before: "})]".to_string(),
10326            ..Default::default()
10327        },
10328        Some(tree_sitter_rust::LANGUAGE.into()),
10329    ));
10330
10331    cx.language_registry().add(language.clone());
10332    cx.update_buffer(|buffer, cx| {
10333        buffer.set_language(Some(language), cx);
10334    });
10335
10336    cx.set_state(
10337        &"
10338            {(ˇ)}
10339            [[ˇ]]
10340            {(ˇ)}
10341        "
10342        .unindent(),
10343    );
10344
10345    cx.update_editor(|editor, window, cx| {
10346        editor.backspace(&Default::default(), window, cx);
10347        editor.backspace(&Default::default(), window, cx);
10348    });
10349
10350    cx.assert_editor_state(
10351        &"
10352            ˇ
10353            ˇ]]
10354            ˇ
10355        "
10356        .unindent(),
10357    );
10358
10359    cx.update_editor(|editor, window, cx| {
10360        editor.handle_input("{", window, cx);
10361        editor.handle_input("{", window, cx);
10362        editor.move_right(&MoveRight, window, cx);
10363        editor.move_right(&MoveRight, window, cx);
10364        editor.move_left(&MoveLeft, window, cx);
10365        editor.move_left(&MoveLeft, window, cx);
10366        editor.backspace(&Default::default(), window, cx);
10367    });
10368
10369    cx.assert_editor_state(
10370        &"
10371            {ˇ}
10372            {ˇ}]]
10373            {ˇ}
10374        "
10375        .unindent(),
10376    );
10377
10378    cx.update_editor(|editor, window, cx| {
10379        editor.backspace(&Default::default(), window, cx);
10380    });
10381
10382    cx.assert_editor_state(
10383        &"
10384            ˇ
10385            ˇ]]
10386            ˇ
10387        "
10388        .unindent(),
10389    );
10390}
10391
10392#[gpui::test]
10393async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
10394    init_test(cx, |_| {});
10395
10396    let language = Arc::new(Language::new(
10397        LanguageConfig::default(),
10398        Some(tree_sitter_rust::LANGUAGE.into()),
10399    ));
10400
10401    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
10402    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10403    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10404    editor
10405        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10406        .await;
10407
10408    editor.update_in(cx, |editor, window, cx| {
10409        editor.set_auto_replace_emoji_shortcode(true);
10410
10411        editor.handle_input("Hello ", window, cx);
10412        editor.handle_input(":wave", window, cx);
10413        assert_eq!(editor.text(cx), "Hello :wave".unindent());
10414
10415        editor.handle_input(":", window, cx);
10416        assert_eq!(editor.text(cx), "Hello 👋".unindent());
10417
10418        editor.handle_input(" :smile", window, cx);
10419        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
10420
10421        editor.handle_input(":", window, cx);
10422        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
10423
10424        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
10425        editor.handle_input(":wave", window, cx);
10426        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
10427
10428        editor.handle_input(":", window, cx);
10429        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
10430
10431        editor.handle_input(":1", window, cx);
10432        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
10433
10434        editor.handle_input(":", window, cx);
10435        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
10436
10437        // Ensure shortcode does not get replaced when it is part of a word
10438        editor.handle_input(" Test:wave", window, cx);
10439        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
10440
10441        editor.handle_input(":", window, cx);
10442        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
10443
10444        editor.set_auto_replace_emoji_shortcode(false);
10445
10446        // Ensure shortcode does not get replaced when auto replace is off
10447        editor.handle_input(" :wave", window, cx);
10448        assert_eq!(
10449            editor.text(cx),
10450            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
10451        );
10452
10453        editor.handle_input(":", window, cx);
10454        assert_eq!(
10455            editor.text(cx),
10456            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
10457        );
10458    });
10459}
10460
10461#[gpui::test]
10462async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
10463    init_test(cx, |_| {});
10464
10465    let (text, insertion_ranges) = marked_text_ranges(
10466        indoc! {"
10467            ˇ
10468        "},
10469        false,
10470    );
10471
10472    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
10473    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10474
10475    _ = editor.update_in(cx, |editor, window, cx| {
10476        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
10477
10478        editor
10479            .insert_snippet(&insertion_ranges, snippet, window, cx)
10480            .unwrap();
10481
10482        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
10483            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
10484            assert_eq!(editor.text(cx), expected_text);
10485            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
10486        }
10487
10488        assert(
10489            editor,
10490            cx,
10491            indoc! {"
10492            type «» =•
10493            "},
10494        );
10495
10496        assert!(editor.context_menu_visible(), "There should be a matches");
10497    });
10498}
10499
10500#[gpui::test]
10501async fn test_snippets(cx: &mut TestAppContext) {
10502    init_test(cx, |_| {});
10503
10504    let mut cx = EditorTestContext::new(cx).await;
10505
10506    cx.set_state(indoc! {"
10507        a.ˇ b
10508        a.ˇ b
10509        a.ˇ b
10510    "});
10511
10512    cx.update_editor(|editor, window, cx| {
10513        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
10514        let insertion_ranges = editor
10515            .selections
10516            .all(cx)
10517            .iter()
10518            .map(|s| s.range())
10519            .collect::<Vec<_>>();
10520        editor
10521            .insert_snippet(&insertion_ranges, snippet, window, cx)
10522            .unwrap();
10523    });
10524
10525    cx.assert_editor_state(indoc! {"
10526        a.f(«oneˇ», two, «threeˇ») b
10527        a.f(«oneˇ», two, «threeˇ») b
10528        a.f(«oneˇ», two, «threeˇ») b
10529    "});
10530
10531    // Can't move earlier than the first tab stop
10532    cx.update_editor(|editor, window, cx| {
10533        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
10534    });
10535    cx.assert_editor_state(indoc! {"
10536        a.f(«oneˇ», two, «threeˇ») b
10537        a.f(«oneˇ», two, «threeˇ») b
10538        a.f(«oneˇ», two, «threeˇ») b
10539    "});
10540
10541    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10542    cx.assert_editor_state(indoc! {"
10543        a.f(one, «twoˇ», three) b
10544        a.f(one, «twoˇ», three) b
10545        a.f(one, «twoˇ», three) b
10546    "});
10547
10548    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
10549    cx.assert_editor_state(indoc! {"
10550        a.f(«oneˇ», two, «threeˇ») b
10551        a.f(«oneˇ», two, «threeˇ») b
10552        a.f(«oneˇ», two, «threeˇ») b
10553    "});
10554
10555    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10556    cx.assert_editor_state(indoc! {"
10557        a.f(one, «twoˇ», three) b
10558        a.f(one, «twoˇ», three) b
10559        a.f(one, «twoˇ», three) b
10560    "});
10561    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10562    cx.assert_editor_state(indoc! {"
10563        a.f(one, two, three)ˇ b
10564        a.f(one, two, three)ˇ b
10565        a.f(one, two, three)ˇ b
10566    "});
10567
10568    // As soon as the last tab stop is reached, snippet state is gone
10569    cx.update_editor(|editor, window, cx| {
10570        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
10571    });
10572    cx.assert_editor_state(indoc! {"
10573        a.f(one, two, three)ˇ b
10574        a.f(one, two, three)ˇ b
10575        a.f(one, two, three)ˇ b
10576    "});
10577}
10578
10579#[gpui::test]
10580async fn test_snippet_indentation(cx: &mut TestAppContext) {
10581    init_test(cx, |_| {});
10582
10583    let mut cx = EditorTestContext::new(cx).await;
10584
10585    cx.update_editor(|editor, window, cx| {
10586        let snippet = Snippet::parse(indoc! {"
10587            /*
10588             * Multiline comment with leading indentation
10589             *
10590             * $1
10591             */
10592            $0"})
10593        .unwrap();
10594        let insertion_ranges = editor
10595            .selections
10596            .all(cx)
10597            .iter()
10598            .map(|s| s.range())
10599            .collect::<Vec<_>>();
10600        editor
10601            .insert_snippet(&insertion_ranges, snippet, window, cx)
10602            .unwrap();
10603    });
10604
10605    cx.assert_editor_state(indoc! {"
10606        /*
10607         * Multiline comment with leading indentation
10608         *
10609         * ˇ
10610         */
10611    "});
10612
10613    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10614    cx.assert_editor_state(indoc! {"
10615        /*
10616         * Multiline comment with leading indentation
10617         *
10618         *•
10619         */
10620        ˇ"});
10621}
10622
10623#[gpui::test]
10624async fn test_document_format_during_save(cx: &mut TestAppContext) {
10625    init_test(cx, |_| {});
10626
10627    let fs = FakeFs::new(cx.executor());
10628    fs.insert_file(path!("/file.rs"), Default::default()).await;
10629
10630    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
10631
10632    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10633    language_registry.add(rust_lang());
10634    let mut fake_servers = language_registry.register_fake_lsp(
10635        "Rust",
10636        FakeLspAdapter {
10637            capabilities: lsp::ServerCapabilities {
10638                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10639                ..Default::default()
10640            },
10641            ..Default::default()
10642        },
10643    );
10644
10645    let buffer = project
10646        .update(cx, |project, cx| {
10647            project.open_local_buffer(path!("/file.rs"), cx)
10648        })
10649        .await
10650        .unwrap();
10651
10652    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10653    let (editor, cx) = cx.add_window_view(|window, cx| {
10654        build_editor_with_project(project.clone(), buffer, window, cx)
10655    });
10656    editor.update_in(cx, |editor, window, cx| {
10657        editor.set_text("one\ntwo\nthree\n", window, cx)
10658    });
10659    assert!(cx.read(|cx| editor.is_dirty(cx)));
10660
10661    cx.executor().start_waiting();
10662    let fake_server = fake_servers.next().await.unwrap();
10663
10664    {
10665        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10666            move |params, _| async move {
10667                assert_eq!(
10668                    params.text_document.uri,
10669                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
10670                );
10671                assert_eq!(params.options.tab_size, 4);
10672                Ok(Some(vec![lsp::TextEdit::new(
10673                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10674                    ", ".to_string(),
10675                )]))
10676            },
10677        );
10678        let save = editor
10679            .update_in(cx, |editor, window, cx| {
10680                editor.save(
10681                    SaveOptions {
10682                        format: true,
10683                        autosave: false,
10684                    },
10685                    project.clone(),
10686                    window,
10687                    cx,
10688                )
10689            })
10690            .unwrap();
10691        cx.executor().start_waiting();
10692        save.await;
10693
10694        assert_eq!(
10695            editor.update(cx, |editor, cx| editor.text(cx)),
10696            "one, two\nthree\n"
10697        );
10698        assert!(!cx.read(|cx| editor.is_dirty(cx)));
10699    }
10700
10701    {
10702        editor.update_in(cx, |editor, window, cx| {
10703            editor.set_text("one\ntwo\nthree\n", window, cx)
10704        });
10705        assert!(cx.read(|cx| editor.is_dirty(cx)));
10706
10707        // Ensure we can still save even if formatting hangs.
10708        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10709            move |params, _| async move {
10710                assert_eq!(
10711                    params.text_document.uri,
10712                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
10713                );
10714                futures::future::pending::<()>().await;
10715                unreachable!()
10716            },
10717        );
10718        let save = editor
10719            .update_in(cx, |editor, window, cx| {
10720                editor.save(
10721                    SaveOptions {
10722                        format: true,
10723                        autosave: false,
10724                    },
10725                    project.clone(),
10726                    window,
10727                    cx,
10728                )
10729            })
10730            .unwrap();
10731        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
10732        cx.executor().start_waiting();
10733        save.await;
10734        assert_eq!(
10735            editor.update(cx, |editor, cx| editor.text(cx)),
10736            "one\ntwo\nthree\n"
10737        );
10738    }
10739
10740    // Set rust language override and assert overridden tabsize is sent to language server
10741    update_test_language_settings(cx, |settings| {
10742        settings.languages.0.insert(
10743            "Rust".into(),
10744            LanguageSettingsContent {
10745                tab_size: NonZeroU32::new(8),
10746                ..Default::default()
10747            },
10748        );
10749    });
10750
10751    {
10752        editor.update_in(cx, |editor, window, cx| {
10753            editor.set_text("somehting_new\n", window, cx)
10754        });
10755        assert!(cx.read(|cx| editor.is_dirty(cx)));
10756        let _formatting_request_signal = fake_server
10757            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
10758                assert_eq!(
10759                    params.text_document.uri,
10760                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
10761                );
10762                assert_eq!(params.options.tab_size, 8);
10763                Ok(Some(vec![]))
10764            });
10765        let save = editor
10766            .update_in(cx, |editor, window, cx| {
10767                editor.save(
10768                    SaveOptions {
10769                        format: true,
10770                        autosave: false,
10771                    },
10772                    project.clone(),
10773                    window,
10774                    cx,
10775                )
10776            })
10777            .unwrap();
10778        cx.executor().start_waiting();
10779        save.await;
10780    }
10781}
10782
10783#[gpui::test]
10784async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
10785    init_test(cx, |settings| {
10786        settings.defaults.ensure_final_newline_on_save = Some(false);
10787    });
10788
10789    let fs = FakeFs::new(cx.executor());
10790    fs.insert_file(path!("/file.txt"), "foo".into()).await;
10791
10792    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
10793
10794    let buffer = project
10795        .update(cx, |project, cx| {
10796            project.open_local_buffer(path!("/file.txt"), cx)
10797        })
10798        .await
10799        .unwrap();
10800
10801    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10802    let (editor, cx) = cx.add_window_view(|window, cx| {
10803        build_editor_with_project(project.clone(), buffer, window, cx)
10804    });
10805    editor.update_in(cx, |editor, window, cx| {
10806        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
10807            s.select_ranges([0..0])
10808        });
10809    });
10810    assert!(!cx.read(|cx| editor.is_dirty(cx)));
10811
10812    editor.update_in(cx, |editor, window, cx| {
10813        editor.handle_input("\n", window, cx)
10814    });
10815    cx.run_until_parked();
10816    save(&editor, &project, cx).await;
10817    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
10818
10819    editor.update_in(cx, |editor, window, cx| {
10820        editor.undo(&Default::default(), window, cx);
10821    });
10822    save(&editor, &project, cx).await;
10823    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
10824
10825    editor.update_in(cx, |editor, window, cx| {
10826        editor.redo(&Default::default(), window, cx);
10827    });
10828    cx.run_until_parked();
10829    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
10830
10831    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
10832        let save = editor
10833            .update_in(cx, |editor, window, cx| {
10834                editor.save(
10835                    SaveOptions {
10836                        format: true,
10837                        autosave: false,
10838                    },
10839                    project.clone(),
10840                    window,
10841                    cx,
10842                )
10843            })
10844            .unwrap();
10845        cx.executor().start_waiting();
10846        save.await;
10847        assert!(!cx.read(|cx| editor.is_dirty(cx)));
10848    }
10849}
10850
10851#[gpui::test]
10852async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
10853    init_test(cx, |_| {});
10854
10855    let cols = 4;
10856    let rows = 10;
10857    let sample_text_1 = sample_text(rows, cols, 'a');
10858    assert_eq!(
10859        sample_text_1,
10860        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
10861    );
10862    let sample_text_2 = sample_text(rows, cols, 'l');
10863    assert_eq!(
10864        sample_text_2,
10865        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
10866    );
10867    let sample_text_3 = sample_text(rows, cols, 'v');
10868    assert_eq!(
10869        sample_text_3,
10870        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
10871    );
10872
10873    let fs = FakeFs::new(cx.executor());
10874    fs.insert_tree(
10875        path!("/a"),
10876        json!({
10877            "main.rs": sample_text_1,
10878            "other.rs": sample_text_2,
10879            "lib.rs": sample_text_3,
10880        }),
10881    )
10882    .await;
10883
10884    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
10885    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
10886    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
10887
10888    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10889    language_registry.add(rust_lang());
10890    let mut fake_servers = language_registry.register_fake_lsp(
10891        "Rust",
10892        FakeLspAdapter {
10893            capabilities: lsp::ServerCapabilities {
10894                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10895                ..Default::default()
10896            },
10897            ..Default::default()
10898        },
10899    );
10900
10901    let worktree = project.update(cx, |project, cx| {
10902        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
10903        assert_eq!(worktrees.len(), 1);
10904        worktrees.pop().unwrap()
10905    });
10906    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
10907
10908    let buffer_1 = project
10909        .update(cx, |project, cx| {
10910            project.open_buffer((worktree_id, "main.rs"), cx)
10911        })
10912        .await
10913        .unwrap();
10914    let buffer_2 = project
10915        .update(cx, |project, cx| {
10916            project.open_buffer((worktree_id, "other.rs"), cx)
10917        })
10918        .await
10919        .unwrap();
10920    let buffer_3 = project
10921        .update(cx, |project, cx| {
10922            project.open_buffer((worktree_id, "lib.rs"), cx)
10923        })
10924        .await
10925        .unwrap();
10926
10927    let multi_buffer = cx.new(|cx| {
10928        let mut multi_buffer = MultiBuffer::new(ReadWrite);
10929        multi_buffer.push_excerpts(
10930            buffer_1.clone(),
10931            [
10932                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
10933                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
10934                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
10935            ],
10936            cx,
10937        );
10938        multi_buffer.push_excerpts(
10939            buffer_2.clone(),
10940            [
10941                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
10942                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
10943                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
10944            ],
10945            cx,
10946        );
10947        multi_buffer.push_excerpts(
10948            buffer_3.clone(),
10949            [
10950                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
10951                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
10952                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
10953            ],
10954            cx,
10955        );
10956        multi_buffer
10957    });
10958    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
10959        Editor::new(
10960            EditorMode::full(),
10961            multi_buffer,
10962            Some(project.clone()),
10963            window,
10964            cx,
10965        )
10966    });
10967
10968    multi_buffer_editor.update_in(cx, |editor, window, cx| {
10969        editor.change_selections(
10970            SelectionEffects::scroll(Autoscroll::Next),
10971            window,
10972            cx,
10973            |s| s.select_ranges(Some(1..2)),
10974        );
10975        editor.insert("|one|two|three|", window, cx);
10976    });
10977    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
10978    multi_buffer_editor.update_in(cx, |editor, window, cx| {
10979        editor.change_selections(
10980            SelectionEffects::scroll(Autoscroll::Next),
10981            window,
10982            cx,
10983            |s| s.select_ranges(Some(60..70)),
10984        );
10985        editor.insert("|four|five|six|", window, cx);
10986    });
10987    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
10988
10989    // First two buffers should be edited, but not the third one.
10990    assert_eq!(
10991        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
10992        "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}",
10993    );
10994    buffer_1.update(cx, |buffer, _| {
10995        assert!(buffer.is_dirty());
10996        assert_eq!(
10997            buffer.text(),
10998            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
10999        )
11000    });
11001    buffer_2.update(cx, |buffer, _| {
11002        assert!(buffer.is_dirty());
11003        assert_eq!(
11004            buffer.text(),
11005            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
11006        )
11007    });
11008    buffer_3.update(cx, |buffer, _| {
11009        assert!(!buffer.is_dirty());
11010        assert_eq!(buffer.text(), sample_text_3,)
11011    });
11012    cx.executor().run_until_parked();
11013
11014    cx.executor().start_waiting();
11015    let save = multi_buffer_editor
11016        .update_in(cx, |editor, window, cx| {
11017            editor.save(
11018                SaveOptions {
11019                    format: true,
11020                    autosave: false,
11021                },
11022                project.clone(),
11023                window,
11024                cx,
11025            )
11026        })
11027        .unwrap();
11028
11029    let fake_server = fake_servers.next().await.unwrap();
11030    fake_server
11031        .server
11032        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
11033            Ok(Some(vec![lsp::TextEdit::new(
11034                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11035                format!("[{} formatted]", params.text_document.uri),
11036            )]))
11037        })
11038        .detach();
11039    save.await;
11040
11041    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
11042    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
11043    assert_eq!(
11044        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11045        uri!(
11046            "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}"
11047        ),
11048    );
11049    buffer_1.update(cx, |buffer, _| {
11050        assert!(!buffer.is_dirty());
11051        assert_eq!(
11052            buffer.text(),
11053            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
11054        )
11055    });
11056    buffer_2.update(cx, |buffer, _| {
11057        assert!(!buffer.is_dirty());
11058        assert_eq!(
11059            buffer.text(),
11060            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
11061        )
11062    });
11063    buffer_3.update(cx, |buffer, _| {
11064        assert!(!buffer.is_dirty());
11065        assert_eq!(buffer.text(), sample_text_3,)
11066    });
11067}
11068
11069#[gpui::test]
11070async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
11071    init_test(cx, |_| {});
11072
11073    let fs = FakeFs::new(cx.executor());
11074    fs.insert_tree(
11075        path!("/dir"),
11076        json!({
11077            "file1.rs": "fn main() { println!(\"hello\"); }",
11078            "file2.rs": "fn test() { println!(\"test\"); }",
11079            "file3.rs": "fn other() { println!(\"other\"); }\n",
11080        }),
11081    )
11082    .await;
11083
11084    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
11085    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11086    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11087
11088    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11089    language_registry.add(rust_lang());
11090
11091    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
11092    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11093
11094    // Open three buffers
11095    let buffer_1 = project
11096        .update(cx, |project, cx| {
11097            project.open_buffer((worktree_id, "file1.rs"), cx)
11098        })
11099        .await
11100        .unwrap();
11101    let buffer_2 = project
11102        .update(cx, |project, cx| {
11103            project.open_buffer((worktree_id, "file2.rs"), cx)
11104        })
11105        .await
11106        .unwrap();
11107    let buffer_3 = project
11108        .update(cx, |project, cx| {
11109            project.open_buffer((worktree_id, "file3.rs"), cx)
11110        })
11111        .await
11112        .unwrap();
11113
11114    // Create a multi-buffer with all three buffers
11115    let multi_buffer = cx.new(|cx| {
11116        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11117        multi_buffer.push_excerpts(
11118            buffer_1.clone(),
11119            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11120            cx,
11121        );
11122        multi_buffer.push_excerpts(
11123            buffer_2.clone(),
11124            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11125            cx,
11126        );
11127        multi_buffer.push_excerpts(
11128            buffer_3.clone(),
11129            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11130            cx,
11131        );
11132        multi_buffer
11133    });
11134
11135    let editor = cx.new_window_entity(|window, cx| {
11136        Editor::new(
11137            EditorMode::full(),
11138            multi_buffer,
11139            Some(project.clone()),
11140            window,
11141            cx,
11142        )
11143    });
11144
11145    // Edit only the first buffer
11146    editor.update_in(cx, |editor, window, cx| {
11147        editor.change_selections(
11148            SelectionEffects::scroll(Autoscroll::Next),
11149            window,
11150            cx,
11151            |s| s.select_ranges(Some(10..10)),
11152        );
11153        editor.insert("// edited", window, cx);
11154    });
11155
11156    // Verify that only buffer 1 is dirty
11157    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
11158    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11159    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11160
11161    // Get write counts after file creation (files were created with initial content)
11162    // We expect each file to have been written once during creation
11163    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
11164    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
11165    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
11166
11167    // Perform autosave
11168    let save_task = editor.update_in(cx, |editor, window, cx| {
11169        editor.save(
11170            SaveOptions {
11171                format: true,
11172                autosave: true,
11173            },
11174            project.clone(),
11175            window,
11176            cx,
11177        )
11178    });
11179    save_task.await.unwrap();
11180
11181    // Only the dirty buffer should have been saved
11182    assert_eq!(
11183        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
11184        1,
11185        "Buffer 1 was dirty, so it should have been written once during autosave"
11186    );
11187    assert_eq!(
11188        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
11189        0,
11190        "Buffer 2 was clean, so it should not have been written during autosave"
11191    );
11192    assert_eq!(
11193        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
11194        0,
11195        "Buffer 3 was clean, so it should not have been written during autosave"
11196    );
11197
11198    // Verify buffer states after autosave
11199    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11200    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11201    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11202
11203    // Now perform a manual save (format = true)
11204    let save_task = editor.update_in(cx, |editor, window, cx| {
11205        editor.save(
11206            SaveOptions {
11207                format: true,
11208                autosave: false,
11209            },
11210            project.clone(),
11211            window,
11212            cx,
11213        )
11214    });
11215    save_task.await.unwrap();
11216
11217    // During manual save, clean buffers don't get written to disk
11218    // They just get did_save called for language server notifications
11219    assert_eq!(
11220        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
11221        1,
11222        "Buffer 1 should only have been written once total (during autosave, not manual save)"
11223    );
11224    assert_eq!(
11225        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
11226        0,
11227        "Buffer 2 should not have been written at all"
11228    );
11229    assert_eq!(
11230        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
11231        0,
11232        "Buffer 3 should not have been written at all"
11233    );
11234}
11235
11236async fn setup_range_format_test(
11237    cx: &mut TestAppContext,
11238) -> (
11239    Entity<Project>,
11240    Entity<Editor>,
11241    &mut gpui::VisualTestContext,
11242    lsp::FakeLanguageServer,
11243) {
11244    init_test(cx, |_| {});
11245
11246    let fs = FakeFs::new(cx.executor());
11247    fs.insert_file(path!("/file.rs"), Default::default()).await;
11248
11249    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11250
11251    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11252    language_registry.add(rust_lang());
11253    let mut fake_servers = language_registry.register_fake_lsp(
11254        "Rust",
11255        FakeLspAdapter {
11256            capabilities: lsp::ServerCapabilities {
11257                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
11258                ..lsp::ServerCapabilities::default()
11259            },
11260            ..FakeLspAdapter::default()
11261        },
11262    );
11263
11264    let buffer = project
11265        .update(cx, |project, cx| {
11266            project.open_local_buffer(path!("/file.rs"), cx)
11267        })
11268        .await
11269        .unwrap();
11270
11271    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11272    let (editor, cx) = cx.add_window_view(|window, cx| {
11273        build_editor_with_project(project.clone(), buffer, window, cx)
11274    });
11275
11276    cx.executor().start_waiting();
11277    let fake_server = fake_servers.next().await.unwrap();
11278
11279    (project, editor, cx, fake_server)
11280}
11281
11282#[gpui::test]
11283async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
11284    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11285
11286    editor.update_in(cx, |editor, window, cx| {
11287        editor.set_text("one\ntwo\nthree\n", window, cx)
11288    });
11289    assert!(cx.read(|cx| editor.is_dirty(cx)));
11290
11291    let save = editor
11292        .update_in(cx, |editor, window, cx| {
11293            editor.save(
11294                SaveOptions {
11295                    format: true,
11296                    autosave: false,
11297                },
11298                project.clone(),
11299                window,
11300                cx,
11301            )
11302        })
11303        .unwrap();
11304    fake_server
11305        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
11306            assert_eq!(
11307                params.text_document.uri,
11308                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11309            );
11310            assert_eq!(params.options.tab_size, 4);
11311            Ok(Some(vec![lsp::TextEdit::new(
11312                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11313                ", ".to_string(),
11314            )]))
11315        })
11316        .next()
11317        .await;
11318    cx.executor().start_waiting();
11319    save.await;
11320    assert_eq!(
11321        editor.update(cx, |editor, cx| editor.text(cx)),
11322        "one, two\nthree\n"
11323    );
11324    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11325}
11326
11327#[gpui::test]
11328async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
11329    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11330
11331    editor.update_in(cx, |editor, window, cx| {
11332        editor.set_text("one\ntwo\nthree\n", window, cx)
11333    });
11334    assert!(cx.read(|cx| editor.is_dirty(cx)));
11335
11336    // Test that save still works when formatting hangs
11337    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
11338        move |params, _| async move {
11339            assert_eq!(
11340                params.text_document.uri,
11341                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11342            );
11343            futures::future::pending::<()>().await;
11344            unreachable!()
11345        },
11346    );
11347    let save = editor
11348        .update_in(cx, |editor, window, cx| {
11349            editor.save(
11350                SaveOptions {
11351                    format: true,
11352                    autosave: false,
11353                },
11354                project.clone(),
11355                window,
11356                cx,
11357            )
11358        })
11359        .unwrap();
11360    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11361    cx.executor().start_waiting();
11362    save.await;
11363    assert_eq!(
11364        editor.update(cx, |editor, cx| editor.text(cx)),
11365        "one\ntwo\nthree\n"
11366    );
11367    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11368}
11369
11370#[gpui::test]
11371async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
11372    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11373
11374    // Buffer starts clean, no formatting should be requested
11375    let save = editor
11376        .update_in(cx, |editor, window, cx| {
11377            editor.save(
11378                SaveOptions {
11379                    format: false,
11380                    autosave: false,
11381                },
11382                project.clone(),
11383                window,
11384                cx,
11385            )
11386        })
11387        .unwrap();
11388    let _pending_format_request = fake_server
11389        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
11390            panic!("Should not be invoked");
11391        })
11392        .next();
11393    cx.executor().start_waiting();
11394    save.await;
11395    cx.run_until_parked();
11396}
11397
11398#[gpui::test]
11399async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
11400    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11401
11402    // Set Rust language override and assert overridden tabsize is sent to language server
11403    update_test_language_settings(cx, |settings| {
11404        settings.languages.0.insert(
11405            "Rust".into(),
11406            LanguageSettingsContent {
11407                tab_size: NonZeroU32::new(8),
11408                ..Default::default()
11409            },
11410        );
11411    });
11412
11413    editor.update_in(cx, |editor, window, cx| {
11414        editor.set_text("something_new\n", window, cx)
11415    });
11416    assert!(cx.read(|cx| editor.is_dirty(cx)));
11417    let save = editor
11418        .update_in(cx, |editor, window, cx| {
11419            editor.save(
11420                SaveOptions {
11421                    format: true,
11422                    autosave: false,
11423                },
11424                project.clone(),
11425                window,
11426                cx,
11427            )
11428        })
11429        .unwrap();
11430    fake_server
11431        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
11432            assert_eq!(
11433                params.text_document.uri,
11434                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11435            );
11436            assert_eq!(params.options.tab_size, 8);
11437            Ok(Some(Vec::new()))
11438        })
11439        .next()
11440        .await;
11441    save.await;
11442}
11443
11444#[gpui::test]
11445async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
11446    init_test(cx, |settings| {
11447        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
11448            Formatter::LanguageServer { name: None },
11449        )))
11450    });
11451
11452    let fs = FakeFs::new(cx.executor());
11453    fs.insert_file(path!("/file.rs"), Default::default()).await;
11454
11455    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11456
11457    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11458    language_registry.add(Arc::new(Language::new(
11459        LanguageConfig {
11460            name: "Rust".into(),
11461            matcher: LanguageMatcher {
11462                path_suffixes: vec!["rs".to_string()],
11463                ..Default::default()
11464            },
11465            ..LanguageConfig::default()
11466        },
11467        Some(tree_sitter_rust::LANGUAGE.into()),
11468    )));
11469    update_test_language_settings(cx, |settings| {
11470        // Enable Prettier formatting for the same buffer, and ensure
11471        // LSP is called instead of Prettier.
11472        settings.defaults.prettier = Some(PrettierSettings {
11473            allowed: true,
11474            ..PrettierSettings::default()
11475        });
11476    });
11477    let mut fake_servers = language_registry.register_fake_lsp(
11478        "Rust",
11479        FakeLspAdapter {
11480            capabilities: lsp::ServerCapabilities {
11481                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11482                ..Default::default()
11483            },
11484            ..Default::default()
11485        },
11486    );
11487
11488    let buffer = project
11489        .update(cx, |project, cx| {
11490            project.open_local_buffer(path!("/file.rs"), cx)
11491        })
11492        .await
11493        .unwrap();
11494
11495    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11496    let (editor, cx) = cx.add_window_view(|window, cx| {
11497        build_editor_with_project(project.clone(), buffer, window, cx)
11498    });
11499    editor.update_in(cx, |editor, window, cx| {
11500        editor.set_text("one\ntwo\nthree\n", window, cx)
11501    });
11502
11503    cx.executor().start_waiting();
11504    let fake_server = fake_servers.next().await.unwrap();
11505
11506    let format = editor
11507        .update_in(cx, |editor, window, cx| {
11508            editor.perform_format(
11509                project.clone(),
11510                FormatTrigger::Manual,
11511                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
11512                window,
11513                cx,
11514            )
11515        })
11516        .unwrap();
11517    fake_server
11518        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
11519            assert_eq!(
11520                params.text_document.uri,
11521                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11522            );
11523            assert_eq!(params.options.tab_size, 4);
11524            Ok(Some(vec![lsp::TextEdit::new(
11525                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11526                ", ".to_string(),
11527            )]))
11528        })
11529        .next()
11530        .await;
11531    cx.executor().start_waiting();
11532    format.await;
11533    assert_eq!(
11534        editor.update(cx, |editor, cx| editor.text(cx)),
11535        "one, two\nthree\n"
11536    );
11537
11538    editor.update_in(cx, |editor, window, cx| {
11539        editor.set_text("one\ntwo\nthree\n", window, cx)
11540    });
11541    // Ensure we don't lock if formatting hangs.
11542    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11543        move |params, _| async move {
11544            assert_eq!(
11545                params.text_document.uri,
11546                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11547            );
11548            futures::future::pending::<()>().await;
11549            unreachable!()
11550        },
11551    );
11552    let format = editor
11553        .update_in(cx, |editor, window, cx| {
11554            editor.perform_format(
11555                project,
11556                FormatTrigger::Manual,
11557                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
11558                window,
11559                cx,
11560            )
11561        })
11562        .unwrap();
11563    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11564    cx.executor().start_waiting();
11565    format.await;
11566    assert_eq!(
11567        editor.update(cx, |editor, cx| editor.text(cx)),
11568        "one\ntwo\nthree\n"
11569    );
11570}
11571
11572#[gpui::test]
11573async fn test_multiple_formatters(cx: &mut TestAppContext) {
11574    init_test(cx, |settings| {
11575        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
11576        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
11577            Formatter::LanguageServer { name: None },
11578            Formatter::CodeActions(
11579                [
11580                    ("code-action-1".into(), true),
11581                    ("code-action-2".into(), true),
11582                ]
11583                .into_iter()
11584                .collect(),
11585            ),
11586        ])))
11587    });
11588
11589    let fs = FakeFs::new(cx.executor());
11590    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
11591        .await;
11592
11593    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11594    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11595    language_registry.add(rust_lang());
11596
11597    let mut fake_servers = language_registry.register_fake_lsp(
11598        "Rust",
11599        FakeLspAdapter {
11600            capabilities: lsp::ServerCapabilities {
11601                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11602                execute_command_provider: Some(lsp::ExecuteCommandOptions {
11603                    commands: vec!["the-command-for-code-action-1".into()],
11604                    ..Default::default()
11605                }),
11606                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
11607                ..Default::default()
11608            },
11609            ..Default::default()
11610        },
11611    );
11612
11613    let buffer = project
11614        .update(cx, |project, cx| {
11615            project.open_local_buffer(path!("/file.rs"), cx)
11616        })
11617        .await
11618        .unwrap();
11619
11620    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11621    let (editor, cx) = cx.add_window_view(|window, cx| {
11622        build_editor_with_project(project.clone(), buffer, window, cx)
11623    });
11624
11625    cx.executor().start_waiting();
11626
11627    let fake_server = fake_servers.next().await.unwrap();
11628    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11629        move |_params, _| async move {
11630            Ok(Some(vec![lsp::TextEdit::new(
11631                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
11632                "applied-formatting\n".to_string(),
11633            )]))
11634        },
11635    );
11636    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
11637        move |params, _| async move {
11638            assert_eq!(
11639                params.context.only,
11640                Some(vec!["code-action-1".into(), "code-action-2".into()])
11641            );
11642            let uri = lsp::Uri::from_file_path(path!("/file.rs")).unwrap();
11643            Ok(Some(vec![
11644                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
11645                    kind: Some("code-action-1".into()),
11646                    edit: Some(lsp::WorkspaceEdit::new(
11647                        [(
11648                            uri.clone(),
11649                            vec![lsp::TextEdit::new(
11650                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
11651                                "applied-code-action-1-edit\n".to_string(),
11652                            )],
11653                        )]
11654                        .into_iter()
11655                        .collect(),
11656                    )),
11657                    command: Some(lsp::Command {
11658                        command: "the-command-for-code-action-1".into(),
11659                        ..Default::default()
11660                    }),
11661                    ..Default::default()
11662                }),
11663                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
11664                    kind: Some("code-action-2".into()),
11665                    edit: Some(lsp::WorkspaceEdit::new(
11666                        [(
11667                            uri,
11668                            vec![lsp::TextEdit::new(
11669                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
11670                                "applied-code-action-2-edit\n".to_string(),
11671                            )],
11672                        )]
11673                        .into_iter()
11674                        .collect(),
11675                    )),
11676                    ..Default::default()
11677                }),
11678            ]))
11679        },
11680    );
11681
11682    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
11683        move |params, _| async move { Ok(params) }
11684    });
11685
11686    let command_lock = Arc::new(futures::lock::Mutex::new(()));
11687    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
11688        let fake = fake_server.clone();
11689        let lock = command_lock.clone();
11690        move |params, _| {
11691            assert_eq!(params.command, "the-command-for-code-action-1");
11692            let fake = fake.clone();
11693            let lock = lock.clone();
11694            async move {
11695                lock.lock().await;
11696                fake.server
11697                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
11698                        label: None,
11699                        edit: lsp::WorkspaceEdit {
11700                            changes: Some(
11701                                [(
11702                                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
11703                                    vec![lsp::TextEdit {
11704                                        range: lsp::Range::new(
11705                                            lsp::Position::new(0, 0),
11706                                            lsp::Position::new(0, 0),
11707                                        ),
11708                                        new_text: "applied-code-action-1-command\n".into(),
11709                                    }],
11710                                )]
11711                                .into_iter()
11712                                .collect(),
11713                            ),
11714                            ..Default::default()
11715                        },
11716                    })
11717                    .await
11718                    .into_response()
11719                    .unwrap();
11720                Ok(Some(json!(null)))
11721            }
11722        }
11723    });
11724
11725    cx.executor().start_waiting();
11726    editor
11727        .update_in(cx, |editor, window, cx| {
11728            editor.perform_format(
11729                project.clone(),
11730                FormatTrigger::Manual,
11731                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
11732                window,
11733                cx,
11734            )
11735        })
11736        .unwrap()
11737        .await;
11738    editor.update(cx, |editor, cx| {
11739        assert_eq!(
11740            editor.text(cx),
11741            r#"
11742                applied-code-action-2-edit
11743                applied-code-action-1-command
11744                applied-code-action-1-edit
11745                applied-formatting
11746                one
11747                two
11748                three
11749            "#
11750            .unindent()
11751        );
11752    });
11753
11754    editor.update_in(cx, |editor, window, cx| {
11755        editor.undo(&Default::default(), window, cx);
11756        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
11757    });
11758
11759    // Perform a manual edit while waiting for an LSP command
11760    // that's being run as part of a formatting code action.
11761    let lock_guard = command_lock.lock().await;
11762    let format = editor
11763        .update_in(cx, |editor, window, cx| {
11764            editor.perform_format(
11765                project.clone(),
11766                FormatTrigger::Manual,
11767                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
11768                window,
11769                cx,
11770            )
11771        })
11772        .unwrap();
11773    cx.run_until_parked();
11774    editor.update(cx, |editor, cx| {
11775        assert_eq!(
11776            editor.text(cx),
11777            r#"
11778                applied-code-action-1-edit
11779                applied-formatting
11780                one
11781                two
11782                three
11783            "#
11784            .unindent()
11785        );
11786
11787        editor.buffer.update(cx, |buffer, cx| {
11788            let ix = buffer.len(cx);
11789            buffer.edit([(ix..ix, "edited\n")], None, cx);
11790        });
11791    });
11792
11793    // Allow the LSP command to proceed. Because the buffer was edited,
11794    // the second code action will not be run.
11795    drop(lock_guard);
11796    format.await;
11797    editor.update_in(cx, |editor, window, cx| {
11798        assert_eq!(
11799            editor.text(cx),
11800            r#"
11801                applied-code-action-1-command
11802                applied-code-action-1-edit
11803                applied-formatting
11804                one
11805                two
11806                three
11807                edited
11808            "#
11809            .unindent()
11810        );
11811
11812        // The manual edit is undone first, because it is the last thing the user did
11813        // (even though the command completed afterwards).
11814        editor.undo(&Default::default(), window, cx);
11815        assert_eq!(
11816            editor.text(cx),
11817            r#"
11818                applied-code-action-1-command
11819                applied-code-action-1-edit
11820                applied-formatting
11821                one
11822                two
11823                three
11824            "#
11825            .unindent()
11826        );
11827
11828        // All the formatting (including the command, which completed after the manual edit)
11829        // is undone together.
11830        editor.undo(&Default::default(), window, cx);
11831        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
11832    });
11833}
11834
11835#[gpui::test]
11836async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
11837    init_test(cx, |settings| {
11838        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
11839            Formatter::LanguageServer { name: None },
11840        ])))
11841    });
11842
11843    let fs = FakeFs::new(cx.executor());
11844    fs.insert_file(path!("/file.ts"), Default::default()).await;
11845
11846    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11847
11848    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11849    language_registry.add(Arc::new(Language::new(
11850        LanguageConfig {
11851            name: "TypeScript".into(),
11852            matcher: LanguageMatcher {
11853                path_suffixes: vec!["ts".to_string()],
11854                ..Default::default()
11855            },
11856            ..LanguageConfig::default()
11857        },
11858        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
11859    )));
11860    update_test_language_settings(cx, |settings| {
11861        settings.defaults.prettier = Some(PrettierSettings {
11862            allowed: true,
11863            ..PrettierSettings::default()
11864        });
11865    });
11866    let mut fake_servers = language_registry.register_fake_lsp(
11867        "TypeScript",
11868        FakeLspAdapter {
11869            capabilities: lsp::ServerCapabilities {
11870                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
11871                ..Default::default()
11872            },
11873            ..Default::default()
11874        },
11875    );
11876
11877    let buffer = project
11878        .update(cx, |project, cx| {
11879            project.open_local_buffer(path!("/file.ts"), cx)
11880        })
11881        .await
11882        .unwrap();
11883
11884    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11885    let (editor, cx) = cx.add_window_view(|window, cx| {
11886        build_editor_with_project(project.clone(), buffer, window, cx)
11887    });
11888    editor.update_in(cx, |editor, window, cx| {
11889        editor.set_text(
11890            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
11891            window,
11892            cx,
11893        )
11894    });
11895
11896    cx.executor().start_waiting();
11897    let fake_server = fake_servers.next().await.unwrap();
11898
11899    let format = editor
11900        .update_in(cx, |editor, window, cx| {
11901            editor.perform_code_action_kind(
11902                project.clone(),
11903                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
11904                window,
11905                cx,
11906            )
11907        })
11908        .unwrap();
11909    fake_server
11910        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
11911            assert_eq!(
11912                params.text_document.uri,
11913                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
11914            );
11915            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
11916                lsp::CodeAction {
11917                    title: "Organize Imports".to_string(),
11918                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
11919                    edit: Some(lsp::WorkspaceEdit {
11920                        changes: Some(
11921                            [(
11922                                params.text_document.uri.clone(),
11923                                vec![lsp::TextEdit::new(
11924                                    lsp::Range::new(
11925                                        lsp::Position::new(1, 0),
11926                                        lsp::Position::new(2, 0),
11927                                    ),
11928                                    "".to_string(),
11929                                )],
11930                            )]
11931                            .into_iter()
11932                            .collect(),
11933                        ),
11934                        ..Default::default()
11935                    }),
11936                    ..Default::default()
11937                },
11938            )]))
11939        })
11940        .next()
11941        .await;
11942    cx.executor().start_waiting();
11943    format.await;
11944    assert_eq!(
11945        editor.update(cx, |editor, cx| editor.text(cx)),
11946        "import { a } from 'module';\n\nconst x = a;\n"
11947    );
11948
11949    editor.update_in(cx, |editor, window, cx| {
11950        editor.set_text(
11951            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
11952            window,
11953            cx,
11954        )
11955    });
11956    // Ensure we don't lock if code action hangs.
11957    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
11958        move |params, _| async move {
11959            assert_eq!(
11960                params.text_document.uri,
11961                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
11962            );
11963            futures::future::pending::<()>().await;
11964            unreachable!()
11965        },
11966    );
11967    let format = editor
11968        .update_in(cx, |editor, window, cx| {
11969            editor.perform_code_action_kind(
11970                project,
11971                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
11972                window,
11973                cx,
11974            )
11975        })
11976        .unwrap();
11977    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
11978    cx.executor().start_waiting();
11979    format.await;
11980    assert_eq!(
11981        editor.update(cx, |editor, cx| editor.text(cx)),
11982        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
11983    );
11984}
11985
11986#[gpui::test]
11987async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
11988    init_test(cx, |_| {});
11989
11990    let mut cx = EditorLspTestContext::new_rust(
11991        lsp::ServerCapabilities {
11992            document_formatting_provider: Some(lsp::OneOf::Left(true)),
11993            ..Default::default()
11994        },
11995        cx,
11996    )
11997    .await;
11998
11999    cx.set_state(indoc! {"
12000        one.twoˇ
12001    "});
12002
12003    // The format request takes a long time. When it completes, it inserts
12004    // a newline and an indent before the `.`
12005    cx.lsp
12006        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
12007            let executor = cx.background_executor().clone();
12008            async move {
12009                executor.timer(Duration::from_millis(100)).await;
12010                Ok(Some(vec![lsp::TextEdit {
12011                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
12012                    new_text: "\n    ".into(),
12013                }]))
12014            }
12015        });
12016
12017    // Submit a format request.
12018    let format_1 = cx
12019        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12020        .unwrap();
12021    cx.executor().run_until_parked();
12022
12023    // Submit a second format request.
12024    let format_2 = cx
12025        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12026        .unwrap();
12027    cx.executor().run_until_parked();
12028
12029    // Wait for both format requests to complete
12030    cx.executor().advance_clock(Duration::from_millis(200));
12031    cx.executor().start_waiting();
12032    format_1.await.unwrap();
12033    cx.executor().start_waiting();
12034    format_2.await.unwrap();
12035
12036    // The formatting edits only happens once.
12037    cx.assert_editor_state(indoc! {"
12038        one
12039            .twoˇ
12040    "});
12041}
12042
12043#[gpui::test]
12044async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
12045    init_test(cx, |settings| {
12046        settings.defaults.formatter = Some(SelectedFormatter::Auto)
12047    });
12048
12049    let mut cx = EditorLspTestContext::new_rust(
12050        lsp::ServerCapabilities {
12051            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12052            ..Default::default()
12053        },
12054        cx,
12055    )
12056    .await;
12057
12058    // Set up a buffer white some trailing whitespace and no trailing newline.
12059    cx.set_state(
12060        &[
12061            "one ",   //
12062            "twoˇ",   //
12063            "three ", //
12064            "four",   //
12065        ]
12066        .join("\n"),
12067    );
12068
12069    // Submit a format request.
12070    let format = cx
12071        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12072        .unwrap();
12073
12074    // Record which buffer changes have been sent to the language server
12075    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
12076    cx.lsp
12077        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
12078            let buffer_changes = buffer_changes.clone();
12079            move |params, _| {
12080                buffer_changes.lock().extend(
12081                    params
12082                        .content_changes
12083                        .into_iter()
12084                        .map(|e| (e.range.unwrap(), e.text)),
12085                );
12086            }
12087        });
12088
12089    // Handle formatting requests to the language server.
12090    cx.lsp
12091        .set_request_handler::<lsp::request::Formatting, _, _>({
12092            let buffer_changes = buffer_changes.clone();
12093            move |_, _| {
12094                // When formatting is requested, trailing whitespace has already been stripped,
12095                // and the trailing newline has already been added.
12096                assert_eq!(
12097                    &buffer_changes.lock()[1..],
12098                    &[
12099                        (
12100                            lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
12101                            "".into()
12102                        ),
12103                        (
12104                            lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
12105                            "".into()
12106                        ),
12107                        (
12108                            lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
12109                            "\n".into()
12110                        ),
12111                    ]
12112                );
12113
12114                // Insert blank lines between each line of the buffer.
12115                async move {
12116                    Ok(Some(vec![
12117                        lsp::TextEdit {
12118                            range: lsp::Range::new(
12119                                lsp::Position::new(1, 0),
12120                                lsp::Position::new(1, 0),
12121                            ),
12122                            new_text: "\n".into(),
12123                        },
12124                        lsp::TextEdit {
12125                            range: lsp::Range::new(
12126                                lsp::Position::new(2, 0),
12127                                lsp::Position::new(2, 0),
12128                            ),
12129                            new_text: "\n".into(),
12130                        },
12131                    ]))
12132                }
12133            }
12134        });
12135
12136    // After formatting the buffer, the trailing whitespace is stripped,
12137    // a newline is appended, and the edits provided by the language server
12138    // have been applied.
12139    format.await.unwrap();
12140    cx.assert_editor_state(
12141        &[
12142            "one",   //
12143            "",      //
12144            "twoˇ",  //
12145            "",      //
12146            "three", //
12147            "four",  //
12148            "",      //
12149        ]
12150        .join("\n"),
12151    );
12152
12153    // Undoing the formatting undoes the trailing whitespace removal, the
12154    // trailing newline, and the LSP edits.
12155    cx.update_buffer(|buffer, cx| buffer.undo(cx));
12156    cx.assert_editor_state(
12157        &[
12158            "one ",   //
12159            "twoˇ",   //
12160            "three ", //
12161            "four",   //
12162        ]
12163        .join("\n"),
12164    );
12165}
12166
12167#[gpui::test]
12168async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
12169    cx: &mut TestAppContext,
12170) {
12171    init_test(cx, |_| {});
12172
12173    cx.update(|cx| {
12174        cx.update_global::<SettingsStore, _>(|settings, cx| {
12175            settings.update_user_settings::<EditorSettings>(cx, |settings| {
12176                settings.auto_signature_help = Some(true);
12177            });
12178        });
12179    });
12180
12181    let mut cx = EditorLspTestContext::new_rust(
12182        lsp::ServerCapabilities {
12183            signature_help_provider: Some(lsp::SignatureHelpOptions {
12184                ..Default::default()
12185            }),
12186            ..Default::default()
12187        },
12188        cx,
12189    )
12190    .await;
12191
12192    let language = Language::new(
12193        LanguageConfig {
12194            name: "Rust".into(),
12195            brackets: BracketPairConfig {
12196                pairs: vec![
12197                    BracketPair {
12198                        start: "{".to_string(),
12199                        end: "}".to_string(),
12200                        close: true,
12201                        surround: true,
12202                        newline: true,
12203                    },
12204                    BracketPair {
12205                        start: "(".to_string(),
12206                        end: ")".to_string(),
12207                        close: true,
12208                        surround: true,
12209                        newline: true,
12210                    },
12211                    BracketPair {
12212                        start: "/*".to_string(),
12213                        end: " */".to_string(),
12214                        close: true,
12215                        surround: true,
12216                        newline: true,
12217                    },
12218                    BracketPair {
12219                        start: "[".to_string(),
12220                        end: "]".to_string(),
12221                        close: false,
12222                        surround: false,
12223                        newline: true,
12224                    },
12225                    BracketPair {
12226                        start: "\"".to_string(),
12227                        end: "\"".to_string(),
12228                        close: true,
12229                        surround: true,
12230                        newline: false,
12231                    },
12232                    BracketPair {
12233                        start: "<".to_string(),
12234                        end: ">".to_string(),
12235                        close: false,
12236                        surround: true,
12237                        newline: true,
12238                    },
12239                ],
12240                ..Default::default()
12241            },
12242            autoclose_before: "})]".to_string(),
12243            ..Default::default()
12244        },
12245        Some(tree_sitter_rust::LANGUAGE.into()),
12246    );
12247    let language = Arc::new(language);
12248
12249    cx.language_registry().add(language.clone());
12250    cx.update_buffer(|buffer, cx| {
12251        buffer.set_language(Some(language), cx);
12252    });
12253
12254    cx.set_state(
12255        &r#"
12256            fn main() {
12257                sampleˇ
12258            }
12259        "#
12260        .unindent(),
12261    );
12262
12263    cx.update_editor(|editor, window, cx| {
12264        editor.handle_input("(", window, cx);
12265    });
12266    cx.assert_editor_state(
12267        &"
12268            fn main() {
12269                sample(ˇ)
12270            }
12271        "
12272        .unindent(),
12273    );
12274
12275    let mocked_response = lsp::SignatureHelp {
12276        signatures: vec![lsp::SignatureInformation {
12277            label: "fn sample(param1: u8, param2: u8)".to_string(),
12278            documentation: None,
12279            parameters: Some(vec![
12280                lsp::ParameterInformation {
12281                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12282                    documentation: None,
12283                },
12284                lsp::ParameterInformation {
12285                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12286                    documentation: None,
12287                },
12288            ]),
12289            active_parameter: None,
12290        }],
12291        active_signature: Some(0),
12292        active_parameter: Some(0),
12293    };
12294    handle_signature_help_request(&mut cx, mocked_response).await;
12295
12296    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12297        .await;
12298
12299    cx.editor(|editor, _, _| {
12300        let signature_help_state = editor.signature_help_state.popover().cloned();
12301        let signature = signature_help_state.unwrap();
12302        assert_eq!(
12303            signature.signatures[signature.current_signature].label,
12304            "fn sample(param1: u8, param2: u8)"
12305        );
12306    });
12307}
12308
12309#[gpui::test]
12310async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
12311    init_test(cx, |_| {});
12312
12313    cx.update(|cx| {
12314        cx.update_global::<SettingsStore, _>(|settings, cx| {
12315            settings.update_user_settings::<EditorSettings>(cx, |settings| {
12316                settings.auto_signature_help = Some(false);
12317                settings.show_signature_help_after_edits = Some(false);
12318            });
12319        });
12320    });
12321
12322    let mut cx = EditorLspTestContext::new_rust(
12323        lsp::ServerCapabilities {
12324            signature_help_provider: Some(lsp::SignatureHelpOptions {
12325                ..Default::default()
12326            }),
12327            ..Default::default()
12328        },
12329        cx,
12330    )
12331    .await;
12332
12333    let language = Language::new(
12334        LanguageConfig {
12335            name: "Rust".into(),
12336            brackets: BracketPairConfig {
12337                pairs: vec![
12338                    BracketPair {
12339                        start: "{".to_string(),
12340                        end: "}".to_string(),
12341                        close: true,
12342                        surround: true,
12343                        newline: true,
12344                    },
12345                    BracketPair {
12346                        start: "(".to_string(),
12347                        end: ")".to_string(),
12348                        close: true,
12349                        surround: true,
12350                        newline: true,
12351                    },
12352                    BracketPair {
12353                        start: "/*".to_string(),
12354                        end: " */".to_string(),
12355                        close: true,
12356                        surround: true,
12357                        newline: true,
12358                    },
12359                    BracketPair {
12360                        start: "[".to_string(),
12361                        end: "]".to_string(),
12362                        close: false,
12363                        surround: false,
12364                        newline: true,
12365                    },
12366                    BracketPair {
12367                        start: "\"".to_string(),
12368                        end: "\"".to_string(),
12369                        close: true,
12370                        surround: true,
12371                        newline: false,
12372                    },
12373                    BracketPair {
12374                        start: "<".to_string(),
12375                        end: ">".to_string(),
12376                        close: false,
12377                        surround: true,
12378                        newline: true,
12379                    },
12380                ],
12381                ..Default::default()
12382            },
12383            autoclose_before: "})]".to_string(),
12384            ..Default::default()
12385        },
12386        Some(tree_sitter_rust::LANGUAGE.into()),
12387    );
12388    let language = Arc::new(language);
12389
12390    cx.language_registry().add(language.clone());
12391    cx.update_buffer(|buffer, cx| {
12392        buffer.set_language(Some(language), cx);
12393    });
12394
12395    // Ensure that signature_help is not called when no signature help is enabled.
12396    cx.set_state(
12397        &r#"
12398            fn main() {
12399                sampleˇ
12400            }
12401        "#
12402        .unindent(),
12403    );
12404    cx.update_editor(|editor, window, cx| {
12405        editor.handle_input("(", window, cx);
12406    });
12407    cx.assert_editor_state(
12408        &"
12409            fn main() {
12410                sample(ˇ)
12411            }
12412        "
12413        .unindent(),
12414    );
12415    cx.editor(|editor, _, _| {
12416        assert!(editor.signature_help_state.task().is_none());
12417    });
12418
12419    let mocked_response = lsp::SignatureHelp {
12420        signatures: vec![lsp::SignatureInformation {
12421            label: "fn sample(param1: u8, param2: u8)".to_string(),
12422            documentation: None,
12423            parameters: Some(vec![
12424                lsp::ParameterInformation {
12425                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12426                    documentation: None,
12427                },
12428                lsp::ParameterInformation {
12429                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12430                    documentation: None,
12431                },
12432            ]),
12433            active_parameter: None,
12434        }],
12435        active_signature: Some(0),
12436        active_parameter: Some(0),
12437    };
12438
12439    // Ensure that signature_help is called when enabled afte edits
12440    cx.update(|_, cx| {
12441        cx.update_global::<SettingsStore, _>(|settings, cx| {
12442            settings.update_user_settings::<EditorSettings>(cx, |settings| {
12443                settings.auto_signature_help = Some(false);
12444                settings.show_signature_help_after_edits = Some(true);
12445            });
12446        });
12447    });
12448    cx.set_state(
12449        &r#"
12450            fn main() {
12451                sampleˇ
12452            }
12453        "#
12454        .unindent(),
12455    );
12456    cx.update_editor(|editor, window, cx| {
12457        editor.handle_input("(", window, cx);
12458    });
12459    cx.assert_editor_state(
12460        &"
12461            fn main() {
12462                sample(ˇ)
12463            }
12464        "
12465        .unindent(),
12466    );
12467    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12468    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12469        .await;
12470    cx.update_editor(|editor, _, _| {
12471        let signature_help_state = editor.signature_help_state.popover().cloned();
12472        assert!(signature_help_state.is_some());
12473        let signature = signature_help_state.unwrap();
12474        assert_eq!(
12475            signature.signatures[signature.current_signature].label,
12476            "fn sample(param1: u8, param2: u8)"
12477        );
12478        editor.signature_help_state = SignatureHelpState::default();
12479    });
12480
12481    // Ensure that signature_help is called when auto signature help override is enabled
12482    cx.update(|_, cx| {
12483        cx.update_global::<SettingsStore, _>(|settings, cx| {
12484            settings.update_user_settings::<EditorSettings>(cx, |settings| {
12485                settings.auto_signature_help = Some(true);
12486                settings.show_signature_help_after_edits = Some(false);
12487            });
12488        });
12489    });
12490    cx.set_state(
12491        &r#"
12492            fn main() {
12493                sampleˇ
12494            }
12495        "#
12496        .unindent(),
12497    );
12498    cx.update_editor(|editor, window, cx| {
12499        editor.handle_input("(", window, cx);
12500    });
12501    cx.assert_editor_state(
12502        &"
12503            fn main() {
12504                sample(ˇ)
12505            }
12506        "
12507        .unindent(),
12508    );
12509    handle_signature_help_request(&mut cx, mocked_response).await;
12510    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12511        .await;
12512    cx.editor(|editor, _, _| {
12513        let signature_help_state = editor.signature_help_state.popover().cloned();
12514        assert!(signature_help_state.is_some());
12515        let signature = signature_help_state.unwrap();
12516        assert_eq!(
12517            signature.signatures[signature.current_signature].label,
12518            "fn sample(param1: u8, param2: u8)"
12519        );
12520    });
12521}
12522
12523#[gpui::test]
12524async fn test_signature_help(cx: &mut TestAppContext) {
12525    init_test(cx, |_| {});
12526    cx.update(|cx| {
12527        cx.update_global::<SettingsStore, _>(|settings, cx| {
12528            settings.update_user_settings::<EditorSettings>(cx, |settings| {
12529                settings.auto_signature_help = Some(true);
12530            });
12531        });
12532    });
12533
12534    let mut cx = EditorLspTestContext::new_rust(
12535        lsp::ServerCapabilities {
12536            signature_help_provider: Some(lsp::SignatureHelpOptions {
12537                ..Default::default()
12538            }),
12539            ..Default::default()
12540        },
12541        cx,
12542    )
12543    .await;
12544
12545    // A test that directly calls `show_signature_help`
12546    cx.update_editor(|editor, window, cx| {
12547        editor.show_signature_help(&ShowSignatureHelp, window, cx);
12548    });
12549
12550    let mocked_response = lsp::SignatureHelp {
12551        signatures: vec![lsp::SignatureInformation {
12552            label: "fn sample(param1: u8, param2: u8)".to_string(),
12553            documentation: None,
12554            parameters: Some(vec![
12555                lsp::ParameterInformation {
12556                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12557                    documentation: None,
12558                },
12559                lsp::ParameterInformation {
12560                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12561                    documentation: None,
12562                },
12563            ]),
12564            active_parameter: None,
12565        }],
12566        active_signature: Some(0),
12567        active_parameter: Some(0),
12568    };
12569    handle_signature_help_request(&mut cx, mocked_response).await;
12570
12571    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12572        .await;
12573
12574    cx.editor(|editor, _, _| {
12575        let signature_help_state = editor.signature_help_state.popover().cloned();
12576        assert!(signature_help_state.is_some());
12577        let signature = signature_help_state.unwrap();
12578        assert_eq!(
12579            signature.signatures[signature.current_signature].label,
12580            "fn sample(param1: u8, param2: u8)"
12581        );
12582    });
12583
12584    // When exiting outside from inside the brackets, `signature_help` is closed.
12585    cx.set_state(indoc! {"
12586        fn main() {
12587            sample(ˇ);
12588        }
12589
12590        fn sample(param1: u8, param2: u8) {}
12591    "});
12592
12593    cx.update_editor(|editor, window, cx| {
12594        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12595            s.select_ranges([0..0])
12596        });
12597    });
12598
12599    let mocked_response = lsp::SignatureHelp {
12600        signatures: Vec::new(),
12601        active_signature: None,
12602        active_parameter: None,
12603    };
12604    handle_signature_help_request(&mut cx, mocked_response).await;
12605
12606    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
12607        .await;
12608
12609    cx.editor(|editor, _, _| {
12610        assert!(!editor.signature_help_state.is_shown());
12611    });
12612
12613    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
12614    cx.set_state(indoc! {"
12615        fn main() {
12616            sample(ˇ);
12617        }
12618
12619        fn sample(param1: u8, param2: u8) {}
12620    "});
12621
12622    let mocked_response = lsp::SignatureHelp {
12623        signatures: vec![lsp::SignatureInformation {
12624            label: "fn sample(param1: u8, param2: u8)".to_string(),
12625            documentation: None,
12626            parameters: Some(vec![
12627                lsp::ParameterInformation {
12628                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12629                    documentation: None,
12630                },
12631                lsp::ParameterInformation {
12632                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12633                    documentation: None,
12634                },
12635            ]),
12636            active_parameter: None,
12637        }],
12638        active_signature: Some(0),
12639        active_parameter: Some(0),
12640    };
12641    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12642    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12643        .await;
12644    cx.editor(|editor, _, _| {
12645        assert!(editor.signature_help_state.is_shown());
12646    });
12647
12648    // Restore the popover with more parameter input
12649    cx.set_state(indoc! {"
12650        fn main() {
12651            sample(param1, param2ˇ);
12652        }
12653
12654        fn sample(param1: u8, param2: u8) {}
12655    "});
12656
12657    let mocked_response = lsp::SignatureHelp {
12658        signatures: vec![lsp::SignatureInformation {
12659            label: "fn sample(param1: u8, param2: u8)".to_string(),
12660            documentation: None,
12661            parameters: Some(vec![
12662                lsp::ParameterInformation {
12663                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12664                    documentation: None,
12665                },
12666                lsp::ParameterInformation {
12667                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12668                    documentation: None,
12669                },
12670            ]),
12671            active_parameter: None,
12672        }],
12673        active_signature: Some(0),
12674        active_parameter: Some(1),
12675    };
12676    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12677    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12678        .await;
12679
12680    // When selecting a range, the popover is gone.
12681    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
12682    cx.update_editor(|editor, window, cx| {
12683        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12684            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
12685        })
12686    });
12687    cx.assert_editor_state(indoc! {"
12688        fn main() {
12689            sample(param1, «ˇparam2»);
12690        }
12691
12692        fn sample(param1: u8, param2: u8) {}
12693    "});
12694    cx.editor(|editor, _, _| {
12695        assert!(!editor.signature_help_state.is_shown());
12696    });
12697
12698    // When unselecting again, the popover is back if within the brackets.
12699    cx.update_editor(|editor, window, cx| {
12700        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12701            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
12702        })
12703    });
12704    cx.assert_editor_state(indoc! {"
12705        fn main() {
12706            sample(param1, ˇparam2);
12707        }
12708
12709        fn sample(param1: u8, param2: u8) {}
12710    "});
12711    handle_signature_help_request(&mut cx, mocked_response).await;
12712    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12713        .await;
12714    cx.editor(|editor, _, _| {
12715        assert!(editor.signature_help_state.is_shown());
12716    });
12717
12718    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
12719    cx.update_editor(|editor, window, cx| {
12720        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12721            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
12722            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
12723        })
12724    });
12725    cx.assert_editor_state(indoc! {"
12726        fn main() {
12727            sample(param1, ˇparam2);
12728        }
12729
12730        fn sample(param1: u8, param2: u8) {}
12731    "});
12732
12733    let mocked_response = lsp::SignatureHelp {
12734        signatures: vec![lsp::SignatureInformation {
12735            label: "fn sample(param1: u8, param2: u8)".to_string(),
12736            documentation: None,
12737            parameters: Some(vec![
12738                lsp::ParameterInformation {
12739                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12740                    documentation: None,
12741                },
12742                lsp::ParameterInformation {
12743                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12744                    documentation: None,
12745                },
12746            ]),
12747            active_parameter: None,
12748        }],
12749        active_signature: Some(0),
12750        active_parameter: Some(1),
12751    };
12752    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12753    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12754        .await;
12755    cx.update_editor(|editor, _, cx| {
12756        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
12757    });
12758    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
12759        .await;
12760    cx.update_editor(|editor, window, cx| {
12761        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12762            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
12763        })
12764    });
12765    cx.assert_editor_state(indoc! {"
12766        fn main() {
12767            sample(param1, «ˇparam2»);
12768        }
12769
12770        fn sample(param1: u8, param2: u8) {}
12771    "});
12772    cx.update_editor(|editor, window, cx| {
12773        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12774            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
12775        })
12776    });
12777    cx.assert_editor_state(indoc! {"
12778        fn main() {
12779            sample(param1, ˇparam2);
12780        }
12781
12782        fn sample(param1: u8, param2: u8) {}
12783    "});
12784    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
12785        .await;
12786}
12787
12788#[gpui::test]
12789async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
12790    init_test(cx, |_| {});
12791
12792    let mut cx = EditorLspTestContext::new_rust(
12793        lsp::ServerCapabilities {
12794            signature_help_provider: Some(lsp::SignatureHelpOptions {
12795                ..Default::default()
12796            }),
12797            ..Default::default()
12798        },
12799        cx,
12800    )
12801    .await;
12802
12803    cx.set_state(indoc! {"
12804        fn main() {
12805            overloadedˇ
12806        }
12807    "});
12808
12809    cx.update_editor(|editor, window, cx| {
12810        editor.handle_input("(", window, cx);
12811        editor.show_signature_help(&ShowSignatureHelp, window, cx);
12812    });
12813
12814    // Mock response with 3 signatures
12815    let mocked_response = lsp::SignatureHelp {
12816        signatures: vec![
12817            lsp::SignatureInformation {
12818                label: "fn overloaded(x: i32)".to_string(),
12819                documentation: None,
12820                parameters: Some(vec![lsp::ParameterInformation {
12821                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
12822                    documentation: None,
12823                }]),
12824                active_parameter: None,
12825            },
12826            lsp::SignatureInformation {
12827                label: "fn overloaded(x: i32, y: i32)".to_string(),
12828                documentation: None,
12829                parameters: Some(vec![
12830                    lsp::ParameterInformation {
12831                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
12832                        documentation: None,
12833                    },
12834                    lsp::ParameterInformation {
12835                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
12836                        documentation: None,
12837                    },
12838                ]),
12839                active_parameter: None,
12840            },
12841            lsp::SignatureInformation {
12842                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
12843                documentation: None,
12844                parameters: Some(vec![
12845                    lsp::ParameterInformation {
12846                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
12847                        documentation: None,
12848                    },
12849                    lsp::ParameterInformation {
12850                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
12851                        documentation: None,
12852                    },
12853                    lsp::ParameterInformation {
12854                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
12855                        documentation: None,
12856                    },
12857                ]),
12858                active_parameter: None,
12859            },
12860        ],
12861        active_signature: Some(1),
12862        active_parameter: Some(0),
12863    };
12864    handle_signature_help_request(&mut cx, mocked_response).await;
12865
12866    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12867        .await;
12868
12869    // Verify we have multiple signatures and the right one is selected
12870    cx.editor(|editor, _, _| {
12871        let popover = editor.signature_help_state.popover().cloned().unwrap();
12872        assert_eq!(popover.signatures.len(), 3);
12873        // active_signature was 1, so that should be the current
12874        assert_eq!(popover.current_signature, 1);
12875        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
12876        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
12877        assert_eq!(
12878            popover.signatures[2].label,
12879            "fn overloaded(x: i32, y: i32, z: i32)"
12880        );
12881    });
12882
12883    // Test navigation functionality
12884    cx.update_editor(|editor, window, cx| {
12885        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
12886    });
12887
12888    cx.editor(|editor, _, _| {
12889        let popover = editor.signature_help_state.popover().cloned().unwrap();
12890        assert_eq!(popover.current_signature, 2);
12891    });
12892
12893    // Test wrap around
12894    cx.update_editor(|editor, window, cx| {
12895        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
12896    });
12897
12898    cx.editor(|editor, _, _| {
12899        let popover = editor.signature_help_state.popover().cloned().unwrap();
12900        assert_eq!(popover.current_signature, 0);
12901    });
12902
12903    // Test previous navigation
12904    cx.update_editor(|editor, window, cx| {
12905        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
12906    });
12907
12908    cx.editor(|editor, _, _| {
12909        let popover = editor.signature_help_state.popover().cloned().unwrap();
12910        assert_eq!(popover.current_signature, 2);
12911    });
12912}
12913
12914#[gpui::test]
12915async fn test_completion_mode(cx: &mut TestAppContext) {
12916    init_test(cx, |_| {});
12917    let mut cx = EditorLspTestContext::new_rust(
12918        lsp::ServerCapabilities {
12919            completion_provider: Some(lsp::CompletionOptions {
12920                resolve_provider: Some(true),
12921                ..Default::default()
12922            }),
12923            ..Default::default()
12924        },
12925        cx,
12926    )
12927    .await;
12928
12929    struct Run {
12930        run_description: &'static str,
12931        initial_state: String,
12932        buffer_marked_text: String,
12933        completion_label: &'static str,
12934        completion_text: &'static str,
12935        expected_with_insert_mode: String,
12936        expected_with_replace_mode: String,
12937        expected_with_replace_subsequence_mode: String,
12938        expected_with_replace_suffix_mode: String,
12939    }
12940
12941    let runs = [
12942        Run {
12943            run_description: "Start of word matches completion text",
12944            initial_state: "before ediˇ after".into(),
12945            buffer_marked_text: "before <edi|> after".into(),
12946            completion_label: "editor",
12947            completion_text: "editor",
12948            expected_with_insert_mode: "before editorˇ after".into(),
12949            expected_with_replace_mode: "before editorˇ after".into(),
12950            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
12951            expected_with_replace_suffix_mode: "before editorˇ after".into(),
12952        },
12953        Run {
12954            run_description: "Accept same text at the middle of the word",
12955            initial_state: "before ediˇtor after".into(),
12956            buffer_marked_text: "before <edi|tor> after".into(),
12957            completion_label: "editor",
12958            completion_text: "editor",
12959            expected_with_insert_mode: "before editorˇtor after".into(),
12960            expected_with_replace_mode: "before editorˇ after".into(),
12961            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
12962            expected_with_replace_suffix_mode: "before editorˇ after".into(),
12963        },
12964        Run {
12965            run_description: "End of word matches completion text -- cursor at end",
12966            initial_state: "before torˇ after".into(),
12967            buffer_marked_text: "before <tor|> after".into(),
12968            completion_label: "editor",
12969            completion_text: "editor",
12970            expected_with_insert_mode: "before editorˇ after".into(),
12971            expected_with_replace_mode: "before editorˇ after".into(),
12972            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
12973            expected_with_replace_suffix_mode: "before editorˇ after".into(),
12974        },
12975        Run {
12976            run_description: "End of word matches completion text -- cursor at start",
12977            initial_state: "before ˇtor after".into(),
12978            buffer_marked_text: "before <|tor> after".into(),
12979            completion_label: "editor",
12980            completion_text: "editor",
12981            expected_with_insert_mode: "before editorˇtor after".into(),
12982            expected_with_replace_mode: "before editorˇ after".into(),
12983            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
12984            expected_with_replace_suffix_mode: "before editorˇ after".into(),
12985        },
12986        Run {
12987            run_description: "Prepend text containing whitespace",
12988            initial_state: "pˇfield: bool".into(),
12989            buffer_marked_text: "<p|field>: bool".into(),
12990            completion_label: "pub ",
12991            completion_text: "pub ",
12992            expected_with_insert_mode: "pub ˇfield: bool".into(),
12993            expected_with_replace_mode: "pub ˇ: bool".into(),
12994            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
12995            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
12996        },
12997        Run {
12998            run_description: "Add element to start of list",
12999            initial_state: "[element_ˇelement_2]".into(),
13000            buffer_marked_text: "[<element_|element_2>]".into(),
13001            completion_label: "element_1",
13002            completion_text: "element_1",
13003            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
13004            expected_with_replace_mode: "[element_1ˇ]".into(),
13005            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
13006            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
13007        },
13008        Run {
13009            run_description: "Add element to start of list -- first and second elements are equal",
13010            initial_state: "[elˇelement]".into(),
13011            buffer_marked_text: "[<el|element>]".into(),
13012            completion_label: "element",
13013            completion_text: "element",
13014            expected_with_insert_mode: "[elementˇelement]".into(),
13015            expected_with_replace_mode: "[elementˇ]".into(),
13016            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
13017            expected_with_replace_suffix_mode: "[elementˇ]".into(),
13018        },
13019        Run {
13020            run_description: "Ends with matching suffix",
13021            initial_state: "SubˇError".into(),
13022            buffer_marked_text: "<Sub|Error>".into(),
13023            completion_label: "SubscriptionError",
13024            completion_text: "SubscriptionError",
13025            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
13026            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13027            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13028            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
13029        },
13030        Run {
13031            run_description: "Suffix is a subsequence -- contiguous",
13032            initial_state: "SubˇErr".into(),
13033            buffer_marked_text: "<Sub|Err>".into(),
13034            completion_label: "SubscriptionError",
13035            completion_text: "SubscriptionError",
13036            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
13037            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13038            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13039            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
13040        },
13041        Run {
13042            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
13043            initial_state: "Suˇscrirr".into(),
13044            buffer_marked_text: "<Su|scrirr>".into(),
13045            completion_label: "SubscriptionError",
13046            completion_text: "SubscriptionError",
13047            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
13048            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13049            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13050            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
13051        },
13052        Run {
13053            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
13054            initial_state: "foo(indˇix)".into(),
13055            buffer_marked_text: "foo(<ind|ix>)".into(),
13056            completion_label: "node_index",
13057            completion_text: "node_index",
13058            expected_with_insert_mode: "foo(node_indexˇix)".into(),
13059            expected_with_replace_mode: "foo(node_indexˇ)".into(),
13060            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
13061            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
13062        },
13063        Run {
13064            run_description: "Replace range ends before cursor - should extend to cursor",
13065            initial_state: "before editˇo after".into(),
13066            buffer_marked_text: "before <{ed}>it|o after".into(),
13067            completion_label: "editor",
13068            completion_text: "editor",
13069            expected_with_insert_mode: "before editorˇo after".into(),
13070            expected_with_replace_mode: "before editorˇo after".into(),
13071            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
13072            expected_with_replace_suffix_mode: "before editorˇo after".into(),
13073        },
13074        Run {
13075            run_description: "Uses label for suffix matching",
13076            initial_state: "before ediˇtor after".into(),
13077            buffer_marked_text: "before <edi|tor> after".into(),
13078            completion_label: "editor",
13079            completion_text: "editor()",
13080            expected_with_insert_mode: "before editor()ˇtor after".into(),
13081            expected_with_replace_mode: "before editor()ˇ after".into(),
13082            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
13083            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
13084        },
13085        Run {
13086            run_description: "Case insensitive subsequence and suffix matching",
13087            initial_state: "before EDiˇtoR after".into(),
13088            buffer_marked_text: "before <EDi|toR> after".into(),
13089            completion_label: "editor",
13090            completion_text: "editor",
13091            expected_with_insert_mode: "before editorˇtoR after".into(),
13092            expected_with_replace_mode: "before editorˇ after".into(),
13093            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13094            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13095        },
13096    ];
13097
13098    for run in runs {
13099        let run_variations = [
13100            (LspInsertMode::Insert, run.expected_with_insert_mode),
13101            (LspInsertMode::Replace, run.expected_with_replace_mode),
13102            (
13103                LspInsertMode::ReplaceSubsequence,
13104                run.expected_with_replace_subsequence_mode,
13105            ),
13106            (
13107                LspInsertMode::ReplaceSuffix,
13108                run.expected_with_replace_suffix_mode,
13109            ),
13110        ];
13111
13112        for (lsp_insert_mode, expected_text) in run_variations {
13113            eprintln!(
13114                "run = {:?}, mode = {lsp_insert_mode:.?}",
13115                run.run_description,
13116            );
13117
13118            update_test_language_settings(&mut cx, |settings| {
13119                settings.defaults.completions = Some(CompletionSettings {
13120                    lsp_insert_mode,
13121                    words: WordsCompletionMode::Disabled,
13122                    words_min_length: 0,
13123                    lsp: true,
13124                    lsp_fetch_timeout_ms: 0,
13125                });
13126            });
13127
13128            cx.set_state(&run.initial_state);
13129            cx.update_editor(|editor, window, cx| {
13130                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13131            });
13132
13133            let counter = Arc::new(AtomicUsize::new(0));
13134            handle_completion_request_with_insert_and_replace(
13135                &mut cx,
13136                &run.buffer_marked_text,
13137                vec![(run.completion_label, run.completion_text)],
13138                counter.clone(),
13139            )
13140            .await;
13141            cx.condition(|editor, _| editor.context_menu_visible())
13142                .await;
13143            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13144
13145            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13146                editor
13147                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
13148                    .unwrap()
13149            });
13150            cx.assert_editor_state(&expected_text);
13151            handle_resolve_completion_request(&mut cx, None).await;
13152            apply_additional_edits.await.unwrap();
13153        }
13154    }
13155}
13156
13157#[gpui::test]
13158async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
13159    init_test(cx, |_| {});
13160    let mut cx = EditorLspTestContext::new_rust(
13161        lsp::ServerCapabilities {
13162            completion_provider: Some(lsp::CompletionOptions {
13163                resolve_provider: Some(true),
13164                ..Default::default()
13165            }),
13166            ..Default::default()
13167        },
13168        cx,
13169    )
13170    .await;
13171
13172    let initial_state = "SubˇError";
13173    let buffer_marked_text = "<Sub|Error>";
13174    let completion_text = "SubscriptionError";
13175    let expected_with_insert_mode = "SubscriptionErrorˇError";
13176    let expected_with_replace_mode = "SubscriptionErrorˇ";
13177
13178    update_test_language_settings(&mut cx, |settings| {
13179        settings.defaults.completions = Some(CompletionSettings {
13180            words: WordsCompletionMode::Disabled,
13181            words_min_length: 0,
13182            // set the opposite here to ensure that the action is overriding the default behavior
13183            lsp_insert_mode: LspInsertMode::Insert,
13184            lsp: true,
13185            lsp_fetch_timeout_ms: 0,
13186        });
13187    });
13188
13189    cx.set_state(initial_state);
13190    cx.update_editor(|editor, window, cx| {
13191        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13192    });
13193
13194    let counter = Arc::new(AtomicUsize::new(0));
13195    handle_completion_request_with_insert_and_replace(
13196        &mut cx,
13197        buffer_marked_text,
13198        vec![(completion_text, completion_text)],
13199        counter.clone(),
13200    )
13201    .await;
13202    cx.condition(|editor, _| editor.context_menu_visible())
13203        .await;
13204    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13205
13206    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13207        editor
13208            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13209            .unwrap()
13210    });
13211    cx.assert_editor_state(expected_with_replace_mode);
13212    handle_resolve_completion_request(&mut cx, None).await;
13213    apply_additional_edits.await.unwrap();
13214
13215    update_test_language_settings(&mut cx, |settings| {
13216        settings.defaults.completions = Some(CompletionSettings {
13217            words: WordsCompletionMode::Disabled,
13218            words_min_length: 0,
13219            // set the opposite here to ensure that the action is overriding the default behavior
13220            lsp_insert_mode: LspInsertMode::Replace,
13221            lsp: true,
13222            lsp_fetch_timeout_ms: 0,
13223        });
13224    });
13225
13226    cx.set_state(initial_state);
13227    cx.update_editor(|editor, window, cx| {
13228        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13229    });
13230    handle_completion_request_with_insert_and_replace(
13231        &mut cx,
13232        buffer_marked_text,
13233        vec![(completion_text, completion_text)],
13234        counter.clone(),
13235    )
13236    .await;
13237    cx.condition(|editor, _| editor.context_menu_visible())
13238        .await;
13239    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
13240
13241    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13242        editor
13243            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
13244            .unwrap()
13245    });
13246    cx.assert_editor_state(expected_with_insert_mode);
13247    handle_resolve_completion_request(&mut cx, None).await;
13248    apply_additional_edits.await.unwrap();
13249}
13250
13251#[gpui::test]
13252async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
13253    init_test(cx, |_| {});
13254    let mut cx = EditorLspTestContext::new_rust(
13255        lsp::ServerCapabilities {
13256            completion_provider: Some(lsp::CompletionOptions {
13257                resolve_provider: Some(true),
13258                ..Default::default()
13259            }),
13260            ..Default::default()
13261        },
13262        cx,
13263    )
13264    .await;
13265
13266    // scenario: surrounding text matches completion text
13267    let completion_text = "to_offset";
13268    let initial_state = indoc! {"
13269        1. buf.to_offˇsuffix
13270        2. buf.to_offˇsuf
13271        3. buf.to_offˇfix
13272        4. buf.to_offˇ
13273        5. into_offˇensive
13274        6. ˇsuffix
13275        7. let ˇ //
13276        8. aaˇzz
13277        9. buf.to_off«zzzzzˇ»suffix
13278        10. buf.«ˇzzzzz»suffix
13279        11. to_off«ˇzzzzz»
13280
13281        buf.to_offˇsuffix  // newest cursor
13282    "};
13283    let completion_marked_buffer = indoc! {"
13284        1. buf.to_offsuffix
13285        2. buf.to_offsuf
13286        3. buf.to_offfix
13287        4. buf.to_off
13288        5. into_offensive
13289        6. suffix
13290        7. let  //
13291        8. aazz
13292        9. buf.to_offzzzzzsuffix
13293        10. buf.zzzzzsuffix
13294        11. to_offzzzzz
13295
13296        buf.<to_off|suffix>  // newest cursor
13297    "};
13298    let expected = indoc! {"
13299        1. buf.to_offsetˇ
13300        2. buf.to_offsetˇsuf
13301        3. buf.to_offsetˇfix
13302        4. buf.to_offsetˇ
13303        5. into_offsetˇensive
13304        6. to_offsetˇsuffix
13305        7. let to_offsetˇ //
13306        8. aato_offsetˇzz
13307        9. buf.to_offsetˇ
13308        10. buf.to_offsetˇsuffix
13309        11. to_offsetˇ
13310
13311        buf.to_offsetˇ  // newest cursor
13312    "};
13313    cx.set_state(initial_state);
13314    cx.update_editor(|editor, window, cx| {
13315        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13316    });
13317    handle_completion_request_with_insert_and_replace(
13318        &mut cx,
13319        completion_marked_buffer,
13320        vec![(completion_text, completion_text)],
13321        Arc::new(AtomicUsize::new(0)),
13322    )
13323    .await;
13324    cx.condition(|editor, _| editor.context_menu_visible())
13325        .await;
13326    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13327        editor
13328            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13329            .unwrap()
13330    });
13331    cx.assert_editor_state(expected);
13332    handle_resolve_completion_request(&mut cx, None).await;
13333    apply_additional_edits.await.unwrap();
13334
13335    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
13336    let completion_text = "foo_and_bar";
13337    let initial_state = indoc! {"
13338        1. ooanbˇ
13339        2. zooanbˇ
13340        3. ooanbˇz
13341        4. zooanbˇz
13342        5. ooanˇ
13343        6. oanbˇ
13344
13345        ooanbˇ
13346    "};
13347    let completion_marked_buffer = indoc! {"
13348        1. ooanb
13349        2. zooanb
13350        3. ooanbz
13351        4. zooanbz
13352        5. ooan
13353        6. oanb
13354
13355        <ooanb|>
13356    "};
13357    let expected = indoc! {"
13358        1. foo_and_barˇ
13359        2. zfoo_and_barˇ
13360        3. foo_and_barˇz
13361        4. zfoo_and_barˇz
13362        5. ooanfoo_and_barˇ
13363        6. oanbfoo_and_barˇ
13364
13365        foo_and_barˇ
13366    "};
13367    cx.set_state(initial_state);
13368    cx.update_editor(|editor, window, cx| {
13369        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13370    });
13371    handle_completion_request_with_insert_and_replace(
13372        &mut cx,
13373        completion_marked_buffer,
13374        vec![(completion_text, completion_text)],
13375        Arc::new(AtomicUsize::new(0)),
13376    )
13377    .await;
13378    cx.condition(|editor, _| editor.context_menu_visible())
13379        .await;
13380    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13381        editor
13382            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13383            .unwrap()
13384    });
13385    cx.assert_editor_state(expected);
13386    handle_resolve_completion_request(&mut cx, None).await;
13387    apply_additional_edits.await.unwrap();
13388
13389    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
13390    // (expects the same as if it was inserted at the end)
13391    let completion_text = "foo_and_bar";
13392    let initial_state = indoc! {"
13393        1. ooˇanb
13394        2. zooˇanb
13395        3. ooˇanbz
13396        4. zooˇanbz
13397
13398        ooˇanb
13399    "};
13400    let completion_marked_buffer = indoc! {"
13401        1. ooanb
13402        2. zooanb
13403        3. ooanbz
13404        4. zooanbz
13405
13406        <oo|anb>
13407    "};
13408    let expected = indoc! {"
13409        1. foo_and_barˇ
13410        2. zfoo_and_barˇ
13411        3. foo_and_barˇz
13412        4. zfoo_and_barˇz
13413
13414        foo_and_barˇ
13415    "};
13416    cx.set_state(initial_state);
13417    cx.update_editor(|editor, window, cx| {
13418        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13419    });
13420    handle_completion_request_with_insert_and_replace(
13421        &mut cx,
13422        completion_marked_buffer,
13423        vec![(completion_text, completion_text)],
13424        Arc::new(AtomicUsize::new(0)),
13425    )
13426    .await;
13427    cx.condition(|editor, _| editor.context_menu_visible())
13428        .await;
13429    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13430        editor
13431            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13432            .unwrap()
13433    });
13434    cx.assert_editor_state(expected);
13435    handle_resolve_completion_request(&mut cx, None).await;
13436    apply_additional_edits.await.unwrap();
13437}
13438
13439// This used to crash
13440#[gpui::test]
13441async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
13442    init_test(cx, |_| {});
13443
13444    let buffer_text = indoc! {"
13445        fn main() {
13446            10.satu;
13447
13448            //
13449            // separate cursors so they open in different excerpts (manually reproducible)
13450            //
13451
13452            10.satu20;
13453        }
13454    "};
13455    let multibuffer_text_with_selections = indoc! {"
13456        fn main() {
13457            10.satuˇ;
13458
13459            //
13460
13461            //
13462
13463            10.satuˇ20;
13464        }
13465    "};
13466    let expected_multibuffer = indoc! {"
13467        fn main() {
13468            10.saturating_sub()ˇ;
13469
13470            //
13471
13472            //
13473
13474            10.saturating_sub()ˇ;
13475        }
13476    "};
13477
13478    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
13479    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
13480
13481    let fs = FakeFs::new(cx.executor());
13482    fs.insert_tree(
13483        path!("/a"),
13484        json!({
13485            "main.rs": buffer_text,
13486        }),
13487    )
13488    .await;
13489
13490    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13491    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13492    language_registry.add(rust_lang());
13493    let mut fake_servers = language_registry.register_fake_lsp(
13494        "Rust",
13495        FakeLspAdapter {
13496            capabilities: lsp::ServerCapabilities {
13497                completion_provider: Some(lsp::CompletionOptions {
13498                    resolve_provider: None,
13499                    ..lsp::CompletionOptions::default()
13500                }),
13501                ..lsp::ServerCapabilities::default()
13502            },
13503            ..FakeLspAdapter::default()
13504        },
13505    );
13506    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13507    let cx = &mut VisualTestContext::from_window(*workspace, cx);
13508    let buffer = project
13509        .update(cx, |project, cx| {
13510            project.open_local_buffer(path!("/a/main.rs"), cx)
13511        })
13512        .await
13513        .unwrap();
13514
13515    let multi_buffer = cx.new(|cx| {
13516        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
13517        multi_buffer.push_excerpts(
13518            buffer.clone(),
13519            [ExcerptRange::new(0..first_excerpt_end)],
13520            cx,
13521        );
13522        multi_buffer.push_excerpts(
13523            buffer.clone(),
13524            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
13525            cx,
13526        );
13527        multi_buffer
13528    });
13529
13530    let editor = workspace
13531        .update(cx, |_, window, cx| {
13532            cx.new(|cx| {
13533                Editor::new(
13534                    EditorMode::Full {
13535                        scale_ui_elements_with_buffer_font_size: false,
13536                        show_active_line_background: false,
13537                        sized_by_content: false,
13538                    },
13539                    multi_buffer.clone(),
13540                    Some(project.clone()),
13541                    window,
13542                    cx,
13543                )
13544            })
13545        })
13546        .unwrap();
13547
13548    let pane = workspace
13549        .update(cx, |workspace, _, _| workspace.active_pane().clone())
13550        .unwrap();
13551    pane.update_in(cx, |pane, window, cx| {
13552        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
13553    });
13554
13555    let fake_server = fake_servers.next().await.unwrap();
13556
13557    editor.update_in(cx, |editor, window, cx| {
13558        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13559            s.select_ranges([
13560                Point::new(1, 11)..Point::new(1, 11),
13561                Point::new(7, 11)..Point::new(7, 11),
13562            ])
13563        });
13564
13565        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
13566    });
13567
13568    editor.update_in(cx, |editor, window, cx| {
13569        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13570    });
13571
13572    fake_server
13573        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13574            let completion_item = lsp::CompletionItem {
13575                label: "saturating_sub()".into(),
13576                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
13577                    lsp::InsertReplaceEdit {
13578                        new_text: "saturating_sub()".to_owned(),
13579                        insert: lsp::Range::new(
13580                            lsp::Position::new(7, 7),
13581                            lsp::Position::new(7, 11),
13582                        ),
13583                        replace: lsp::Range::new(
13584                            lsp::Position::new(7, 7),
13585                            lsp::Position::new(7, 13),
13586                        ),
13587                    },
13588                )),
13589                ..lsp::CompletionItem::default()
13590            };
13591
13592            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
13593        })
13594        .next()
13595        .await
13596        .unwrap();
13597
13598    cx.condition(&editor, |editor, _| editor.context_menu_visible())
13599        .await;
13600
13601    editor
13602        .update_in(cx, |editor, window, cx| {
13603            editor
13604                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13605                .unwrap()
13606        })
13607        .await
13608        .unwrap();
13609
13610    editor.update(cx, |editor, cx| {
13611        assert_text_with_selections(editor, expected_multibuffer, cx);
13612    })
13613}
13614
13615#[gpui::test]
13616async fn test_completion(cx: &mut TestAppContext) {
13617    init_test(cx, |_| {});
13618
13619    let mut cx = EditorLspTestContext::new_rust(
13620        lsp::ServerCapabilities {
13621            completion_provider: Some(lsp::CompletionOptions {
13622                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
13623                resolve_provider: Some(true),
13624                ..Default::default()
13625            }),
13626            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13627            ..Default::default()
13628        },
13629        cx,
13630    )
13631    .await;
13632    let counter = Arc::new(AtomicUsize::new(0));
13633
13634    cx.set_state(indoc! {"
13635        oneˇ
13636        two
13637        three
13638    "});
13639    cx.simulate_keystroke(".");
13640    handle_completion_request(
13641        indoc! {"
13642            one.|<>
13643            two
13644            three
13645        "},
13646        vec!["first_completion", "second_completion"],
13647        true,
13648        counter.clone(),
13649        &mut cx,
13650    )
13651    .await;
13652    cx.condition(|editor, _| editor.context_menu_visible())
13653        .await;
13654    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13655
13656    let _handler = handle_signature_help_request(
13657        &mut cx,
13658        lsp::SignatureHelp {
13659            signatures: vec![lsp::SignatureInformation {
13660                label: "test signature".to_string(),
13661                documentation: None,
13662                parameters: Some(vec![lsp::ParameterInformation {
13663                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
13664                    documentation: None,
13665                }]),
13666                active_parameter: None,
13667            }],
13668            active_signature: None,
13669            active_parameter: None,
13670        },
13671    );
13672    cx.update_editor(|editor, window, cx| {
13673        assert!(
13674            !editor.signature_help_state.is_shown(),
13675            "No signature help was called for"
13676        );
13677        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13678    });
13679    cx.run_until_parked();
13680    cx.update_editor(|editor, _, _| {
13681        assert!(
13682            !editor.signature_help_state.is_shown(),
13683            "No signature help should be shown when completions menu is open"
13684        );
13685    });
13686
13687    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13688        editor.context_menu_next(&Default::default(), window, cx);
13689        editor
13690            .confirm_completion(&ConfirmCompletion::default(), window, cx)
13691            .unwrap()
13692    });
13693    cx.assert_editor_state(indoc! {"
13694        one.second_completionˇ
13695        two
13696        three
13697    "});
13698
13699    handle_resolve_completion_request(
13700        &mut cx,
13701        Some(vec![
13702            (
13703                //This overlaps with the primary completion edit which is
13704                //misbehavior from the LSP spec, test that we filter it out
13705                indoc! {"
13706                    one.second_ˇcompletion
13707                    two
13708                    threeˇ
13709                "},
13710                "overlapping additional edit",
13711            ),
13712            (
13713                indoc! {"
13714                    one.second_completion
13715                    two
13716                    threeˇ
13717                "},
13718                "\nadditional edit",
13719            ),
13720        ]),
13721    )
13722    .await;
13723    apply_additional_edits.await.unwrap();
13724    cx.assert_editor_state(indoc! {"
13725        one.second_completionˇ
13726        two
13727        three
13728        additional edit
13729    "});
13730
13731    cx.set_state(indoc! {"
13732        one.second_completion
13733        twoˇ
13734        threeˇ
13735        additional edit
13736    "});
13737    cx.simulate_keystroke(" ");
13738    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
13739    cx.simulate_keystroke("s");
13740    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
13741
13742    cx.assert_editor_state(indoc! {"
13743        one.second_completion
13744        two sˇ
13745        three sˇ
13746        additional edit
13747    "});
13748    handle_completion_request(
13749        indoc! {"
13750            one.second_completion
13751            two s
13752            three <s|>
13753            additional edit
13754        "},
13755        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
13756        true,
13757        counter.clone(),
13758        &mut cx,
13759    )
13760    .await;
13761    cx.condition(|editor, _| editor.context_menu_visible())
13762        .await;
13763    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
13764
13765    cx.simulate_keystroke("i");
13766
13767    handle_completion_request(
13768        indoc! {"
13769            one.second_completion
13770            two si
13771            three <si|>
13772            additional edit
13773        "},
13774        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
13775        true,
13776        counter.clone(),
13777        &mut cx,
13778    )
13779    .await;
13780    cx.condition(|editor, _| editor.context_menu_visible())
13781        .await;
13782    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
13783
13784    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13785        editor
13786            .confirm_completion(&ConfirmCompletion::default(), window, cx)
13787            .unwrap()
13788    });
13789    cx.assert_editor_state(indoc! {"
13790        one.second_completion
13791        two sixth_completionˇ
13792        three sixth_completionˇ
13793        additional edit
13794    "});
13795
13796    apply_additional_edits.await.unwrap();
13797
13798    update_test_language_settings(&mut cx, |settings| {
13799        settings.defaults.show_completions_on_input = Some(false);
13800    });
13801    cx.set_state("editorˇ");
13802    cx.simulate_keystroke(".");
13803    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
13804    cx.simulate_keystrokes("c l o");
13805    cx.assert_editor_state("editor.cloˇ");
13806    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
13807    cx.update_editor(|editor, window, cx| {
13808        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13809    });
13810    handle_completion_request(
13811        "editor.<clo|>",
13812        vec!["close", "clobber"],
13813        true,
13814        counter.clone(),
13815        &mut cx,
13816    )
13817    .await;
13818    cx.condition(|editor, _| editor.context_menu_visible())
13819        .await;
13820    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
13821
13822    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13823        editor
13824            .confirm_completion(&ConfirmCompletion::default(), window, cx)
13825            .unwrap()
13826    });
13827    cx.assert_editor_state("editor.clobberˇ");
13828    handle_resolve_completion_request(&mut cx, None).await;
13829    apply_additional_edits.await.unwrap();
13830}
13831
13832#[gpui::test]
13833async fn test_completion_reuse(cx: &mut TestAppContext) {
13834    init_test(cx, |_| {});
13835
13836    let mut cx = EditorLspTestContext::new_rust(
13837        lsp::ServerCapabilities {
13838            completion_provider: Some(lsp::CompletionOptions {
13839                trigger_characters: Some(vec![".".to_string()]),
13840                ..Default::default()
13841            }),
13842            ..Default::default()
13843        },
13844        cx,
13845    )
13846    .await;
13847
13848    let counter = Arc::new(AtomicUsize::new(0));
13849    cx.set_state("objˇ");
13850    cx.simulate_keystroke(".");
13851
13852    // Initial completion request returns complete results
13853    let is_incomplete = false;
13854    handle_completion_request(
13855        "obj.|<>",
13856        vec!["a", "ab", "abc"],
13857        is_incomplete,
13858        counter.clone(),
13859        &mut cx,
13860    )
13861    .await;
13862    cx.run_until_parked();
13863    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13864    cx.assert_editor_state("obj.ˇ");
13865    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
13866
13867    // Type "a" - filters existing completions
13868    cx.simulate_keystroke("a");
13869    cx.run_until_parked();
13870    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13871    cx.assert_editor_state("obj.aˇ");
13872    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
13873
13874    // Type "b" - filters existing completions
13875    cx.simulate_keystroke("b");
13876    cx.run_until_parked();
13877    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13878    cx.assert_editor_state("obj.abˇ");
13879    check_displayed_completions(vec!["ab", "abc"], &mut cx);
13880
13881    // Type "c" - filters existing completions
13882    cx.simulate_keystroke("c");
13883    cx.run_until_parked();
13884    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13885    cx.assert_editor_state("obj.abcˇ");
13886    check_displayed_completions(vec!["abc"], &mut cx);
13887
13888    // Backspace to delete "c" - filters existing completions
13889    cx.update_editor(|editor, window, cx| {
13890        editor.backspace(&Backspace, window, cx);
13891    });
13892    cx.run_until_parked();
13893    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13894    cx.assert_editor_state("obj.abˇ");
13895    check_displayed_completions(vec!["ab", "abc"], &mut cx);
13896
13897    // Moving cursor to the left dismisses menu.
13898    cx.update_editor(|editor, window, cx| {
13899        editor.move_left(&MoveLeft, window, cx);
13900    });
13901    cx.run_until_parked();
13902    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13903    cx.assert_editor_state("obj.aˇb");
13904    cx.update_editor(|editor, _, _| {
13905        assert_eq!(editor.context_menu_visible(), false);
13906    });
13907
13908    // Type "b" - new request
13909    cx.simulate_keystroke("b");
13910    let is_incomplete = false;
13911    handle_completion_request(
13912        "obj.<ab|>a",
13913        vec!["ab", "abc"],
13914        is_incomplete,
13915        counter.clone(),
13916        &mut cx,
13917    )
13918    .await;
13919    cx.run_until_parked();
13920    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
13921    cx.assert_editor_state("obj.abˇb");
13922    check_displayed_completions(vec!["ab", "abc"], &mut cx);
13923
13924    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
13925    cx.update_editor(|editor, window, cx| {
13926        editor.backspace(&Backspace, window, cx);
13927    });
13928    let is_incomplete = false;
13929    handle_completion_request(
13930        "obj.<a|>b",
13931        vec!["a", "ab", "abc"],
13932        is_incomplete,
13933        counter.clone(),
13934        &mut cx,
13935    )
13936    .await;
13937    cx.run_until_parked();
13938    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
13939    cx.assert_editor_state("obj.aˇb");
13940    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
13941
13942    // Backspace to delete "a" - dismisses menu.
13943    cx.update_editor(|editor, window, cx| {
13944        editor.backspace(&Backspace, window, cx);
13945    });
13946    cx.run_until_parked();
13947    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
13948    cx.assert_editor_state("obj.ˇb");
13949    cx.update_editor(|editor, _, _| {
13950        assert_eq!(editor.context_menu_visible(), false);
13951    });
13952}
13953
13954#[gpui::test]
13955async fn test_word_completion(cx: &mut TestAppContext) {
13956    let lsp_fetch_timeout_ms = 10;
13957    init_test(cx, |language_settings| {
13958        language_settings.defaults.completions = Some(CompletionSettings {
13959            words: WordsCompletionMode::Fallback,
13960            words_min_length: 0,
13961            lsp: true,
13962            lsp_fetch_timeout_ms: 10,
13963            lsp_insert_mode: LspInsertMode::Insert,
13964        });
13965    });
13966
13967    let mut cx = EditorLspTestContext::new_rust(
13968        lsp::ServerCapabilities {
13969            completion_provider: Some(lsp::CompletionOptions {
13970                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
13971                ..lsp::CompletionOptions::default()
13972            }),
13973            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13974            ..lsp::ServerCapabilities::default()
13975        },
13976        cx,
13977    )
13978    .await;
13979
13980    let throttle_completions = Arc::new(AtomicBool::new(false));
13981
13982    let lsp_throttle_completions = throttle_completions.clone();
13983    let _completion_requests_handler =
13984        cx.lsp
13985            .server
13986            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
13987                let lsp_throttle_completions = lsp_throttle_completions.clone();
13988                let cx = cx.clone();
13989                async move {
13990                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
13991                        cx.background_executor()
13992                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
13993                            .await;
13994                    }
13995                    Ok(Some(lsp::CompletionResponse::Array(vec![
13996                        lsp::CompletionItem {
13997                            label: "first".into(),
13998                            ..lsp::CompletionItem::default()
13999                        },
14000                        lsp::CompletionItem {
14001                            label: "last".into(),
14002                            ..lsp::CompletionItem::default()
14003                        },
14004                    ])))
14005                }
14006            });
14007
14008    cx.set_state(indoc! {"
14009        oneˇ
14010        two
14011        three
14012    "});
14013    cx.simulate_keystroke(".");
14014    cx.executor().run_until_parked();
14015    cx.condition(|editor, _| editor.context_menu_visible())
14016        .await;
14017    cx.update_editor(|editor, window, cx| {
14018        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14019        {
14020            assert_eq!(
14021                completion_menu_entries(menu),
14022                &["first", "last"],
14023                "When LSP server is fast to reply, no fallback word completions are used"
14024            );
14025        } else {
14026            panic!("expected completion menu to be open");
14027        }
14028        editor.cancel(&Cancel, window, cx);
14029    });
14030    cx.executor().run_until_parked();
14031    cx.condition(|editor, _| !editor.context_menu_visible())
14032        .await;
14033
14034    throttle_completions.store(true, atomic::Ordering::Release);
14035    cx.simulate_keystroke(".");
14036    cx.executor()
14037        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
14038    cx.executor().run_until_parked();
14039    cx.condition(|editor, _| editor.context_menu_visible())
14040        .await;
14041    cx.update_editor(|editor, _, _| {
14042        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14043        {
14044            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
14045                "When LSP server is slow, document words can be shown instead, if configured accordingly");
14046        } else {
14047            panic!("expected completion menu to be open");
14048        }
14049    });
14050}
14051
14052#[gpui::test]
14053async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
14054    init_test(cx, |language_settings| {
14055        language_settings.defaults.completions = Some(CompletionSettings {
14056            words: WordsCompletionMode::Enabled,
14057            words_min_length: 0,
14058            lsp: true,
14059            lsp_fetch_timeout_ms: 0,
14060            lsp_insert_mode: LspInsertMode::Insert,
14061        });
14062    });
14063
14064    let mut cx = EditorLspTestContext::new_rust(
14065        lsp::ServerCapabilities {
14066            completion_provider: Some(lsp::CompletionOptions {
14067                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14068                ..lsp::CompletionOptions::default()
14069            }),
14070            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14071            ..lsp::ServerCapabilities::default()
14072        },
14073        cx,
14074    )
14075    .await;
14076
14077    let _completion_requests_handler =
14078        cx.lsp
14079            .server
14080            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14081                Ok(Some(lsp::CompletionResponse::Array(vec![
14082                    lsp::CompletionItem {
14083                        label: "first".into(),
14084                        ..lsp::CompletionItem::default()
14085                    },
14086                    lsp::CompletionItem {
14087                        label: "last".into(),
14088                        ..lsp::CompletionItem::default()
14089                    },
14090                ])))
14091            });
14092
14093    cx.set_state(indoc! {"ˇ
14094        first
14095        last
14096        second
14097    "});
14098    cx.simulate_keystroke(".");
14099    cx.executor().run_until_parked();
14100    cx.condition(|editor, _| editor.context_menu_visible())
14101        .await;
14102    cx.update_editor(|editor, _, _| {
14103        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14104        {
14105            assert_eq!(
14106                completion_menu_entries(menu),
14107                &["first", "last", "second"],
14108                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
14109            );
14110        } else {
14111            panic!("expected completion menu to be open");
14112        }
14113    });
14114}
14115
14116#[gpui::test]
14117async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
14118    init_test(cx, |language_settings| {
14119        language_settings.defaults.completions = Some(CompletionSettings {
14120            words: WordsCompletionMode::Disabled,
14121            words_min_length: 0,
14122            lsp: true,
14123            lsp_fetch_timeout_ms: 0,
14124            lsp_insert_mode: LspInsertMode::Insert,
14125        });
14126    });
14127
14128    let mut cx = EditorLspTestContext::new_rust(
14129        lsp::ServerCapabilities {
14130            completion_provider: Some(lsp::CompletionOptions {
14131                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14132                ..lsp::CompletionOptions::default()
14133            }),
14134            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14135            ..lsp::ServerCapabilities::default()
14136        },
14137        cx,
14138    )
14139    .await;
14140
14141    let _completion_requests_handler =
14142        cx.lsp
14143            .server
14144            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14145                panic!("LSP completions should not be queried when dealing with word completions")
14146            });
14147
14148    cx.set_state(indoc! {"ˇ
14149        first
14150        last
14151        second
14152    "});
14153    cx.update_editor(|editor, window, cx| {
14154        editor.show_word_completions(&ShowWordCompletions, window, cx);
14155    });
14156    cx.executor().run_until_parked();
14157    cx.condition(|editor, _| editor.context_menu_visible())
14158        .await;
14159    cx.update_editor(|editor, _, _| {
14160        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14161        {
14162            assert_eq!(
14163                completion_menu_entries(menu),
14164                &["first", "last", "second"],
14165                "`ShowWordCompletions` action should show word completions"
14166            );
14167        } else {
14168            panic!("expected completion menu to be open");
14169        }
14170    });
14171
14172    cx.simulate_keystroke("l");
14173    cx.executor().run_until_parked();
14174    cx.condition(|editor, _| editor.context_menu_visible())
14175        .await;
14176    cx.update_editor(|editor, _, _| {
14177        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14178        {
14179            assert_eq!(
14180                completion_menu_entries(menu),
14181                &["last"],
14182                "After showing word completions, further editing should filter them and not query the LSP"
14183            );
14184        } else {
14185            panic!("expected completion menu to be open");
14186        }
14187    });
14188}
14189
14190#[gpui::test]
14191async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
14192    init_test(cx, |language_settings| {
14193        language_settings.defaults.completions = Some(CompletionSettings {
14194            words: WordsCompletionMode::Fallback,
14195            words_min_length: 0,
14196            lsp: false,
14197            lsp_fetch_timeout_ms: 0,
14198            lsp_insert_mode: LspInsertMode::Insert,
14199        });
14200    });
14201
14202    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14203
14204    cx.set_state(indoc! {"ˇ
14205        0_usize
14206        let
14207        33
14208        4.5f32
14209    "});
14210    cx.update_editor(|editor, window, cx| {
14211        editor.show_completions(&ShowCompletions::default(), window, cx);
14212    });
14213    cx.executor().run_until_parked();
14214    cx.condition(|editor, _| editor.context_menu_visible())
14215        .await;
14216    cx.update_editor(|editor, window, cx| {
14217        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14218        {
14219            assert_eq!(
14220                completion_menu_entries(menu),
14221                &["let"],
14222                "With no digits in the completion query, no digits should be in the word completions"
14223            );
14224        } else {
14225            panic!("expected completion menu to be open");
14226        }
14227        editor.cancel(&Cancel, window, cx);
14228    });
14229
14230    cx.set_state(indoc! {"14231        0_usize
14232        let
14233        3
14234        33.35f32
14235    "});
14236    cx.update_editor(|editor, window, cx| {
14237        editor.show_completions(&ShowCompletions::default(), window, cx);
14238    });
14239    cx.executor().run_until_parked();
14240    cx.condition(|editor, _| editor.context_menu_visible())
14241        .await;
14242    cx.update_editor(|editor, _, _| {
14243        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14244        {
14245            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
14246                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
14247        } else {
14248            panic!("expected completion menu to be open");
14249        }
14250    });
14251}
14252
14253#[gpui::test]
14254async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
14255    init_test(cx, |language_settings| {
14256        language_settings.defaults.completions = Some(CompletionSettings {
14257            words: WordsCompletionMode::Enabled,
14258            words_min_length: 3,
14259            lsp: true,
14260            lsp_fetch_timeout_ms: 0,
14261            lsp_insert_mode: LspInsertMode::Insert,
14262        });
14263    });
14264
14265    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14266    cx.set_state(indoc! {"ˇ
14267        wow
14268        wowen
14269        wowser
14270    "});
14271    cx.simulate_keystroke("w");
14272    cx.executor().run_until_parked();
14273    cx.update_editor(|editor, _, _| {
14274        if editor.context_menu.borrow_mut().is_some() {
14275            panic!(
14276                "expected completion menu to be hidden, as words completion threshold is not met"
14277            );
14278        }
14279    });
14280
14281    cx.update_editor(|editor, window, cx| {
14282        editor.show_word_completions(&ShowWordCompletions, window, cx);
14283    });
14284    cx.executor().run_until_parked();
14285    cx.update_editor(|editor, window, cx| {
14286        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14287        {
14288            assert_eq!(completion_menu_entries(menu), &["wowser", "wowen", "wow"], "Even though the threshold is not met, invoking word completions with an action should provide the completions");
14289        } else {
14290            panic!("expected completion menu to be open after the word completions are called with an action");
14291        }
14292
14293        editor.cancel(&Cancel, window, cx);
14294    });
14295    cx.update_editor(|editor, _, _| {
14296        if editor.context_menu.borrow_mut().is_some() {
14297            panic!("expected completion menu to be hidden after canceling");
14298        }
14299    });
14300
14301    cx.simulate_keystroke("o");
14302    cx.executor().run_until_parked();
14303    cx.update_editor(|editor, _, _| {
14304        if editor.context_menu.borrow_mut().is_some() {
14305            panic!(
14306                "expected completion menu to be hidden, as words completion threshold is not met still"
14307            );
14308        }
14309    });
14310
14311    cx.simulate_keystroke("w");
14312    cx.executor().run_until_parked();
14313    cx.update_editor(|editor, _, _| {
14314        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14315        {
14316            assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
14317        } else {
14318            panic!("expected completion menu to be open after the word completions threshold is met");
14319        }
14320    });
14321}
14322
14323#[gpui::test]
14324async fn test_word_completions_disabled(cx: &mut TestAppContext) {
14325    init_test(cx, |language_settings| {
14326        language_settings.defaults.completions = Some(CompletionSettings {
14327            words: WordsCompletionMode::Enabled,
14328            words_min_length: 0,
14329            lsp: true,
14330            lsp_fetch_timeout_ms: 0,
14331            lsp_insert_mode: LspInsertMode::Insert,
14332        });
14333    });
14334
14335    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14336    cx.update_editor(|editor, _, _| {
14337        editor.disable_word_completions();
14338    });
14339    cx.set_state(indoc! {"ˇ
14340        wow
14341        wowen
14342        wowser
14343    "});
14344    cx.simulate_keystroke("w");
14345    cx.executor().run_until_parked();
14346    cx.update_editor(|editor, _, _| {
14347        if editor.context_menu.borrow_mut().is_some() {
14348            panic!(
14349                "expected completion menu to be hidden, as words completion are disabled for this editor"
14350            );
14351        }
14352    });
14353
14354    cx.update_editor(|editor, window, cx| {
14355        editor.show_word_completions(&ShowWordCompletions, window, cx);
14356    });
14357    cx.executor().run_until_parked();
14358    cx.update_editor(|editor, _, _| {
14359        if editor.context_menu.borrow_mut().is_some() {
14360            panic!(
14361                "expected completion menu to be hidden even if called for explicitly, as words completion are disabled for this editor"
14362            );
14363        }
14364    });
14365}
14366
14367fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
14368    let position = || lsp::Position {
14369        line: params.text_document_position.position.line,
14370        character: params.text_document_position.position.character,
14371    };
14372    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14373        range: lsp::Range {
14374            start: position(),
14375            end: position(),
14376        },
14377        new_text: text.to_string(),
14378    }))
14379}
14380
14381#[gpui::test]
14382async fn test_multiline_completion(cx: &mut TestAppContext) {
14383    init_test(cx, |_| {});
14384
14385    let fs = FakeFs::new(cx.executor());
14386    fs.insert_tree(
14387        path!("/a"),
14388        json!({
14389            "main.ts": "a",
14390        }),
14391    )
14392    .await;
14393
14394    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14395    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14396    let typescript_language = Arc::new(Language::new(
14397        LanguageConfig {
14398            name: "TypeScript".into(),
14399            matcher: LanguageMatcher {
14400                path_suffixes: vec!["ts".to_string()],
14401                ..LanguageMatcher::default()
14402            },
14403            line_comments: vec!["// ".into()],
14404            ..LanguageConfig::default()
14405        },
14406        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
14407    ));
14408    language_registry.add(typescript_language.clone());
14409    let mut fake_servers = language_registry.register_fake_lsp(
14410        "TypeScript",
14411        FakeLspAdapter {
14412            capabilities: lsp::ServerCapabilities {
14413                completion_provider: Some(lsp::CompletionOptions {
14414                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14415                    ..lsp::CompletionOptions::default()
14416                }),
14417                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14418                ..lsp::ServerCapabilities::default()
14419            },
14420            // Emulate vtsls label generation
14421            label_for_completion: Some(Box::new(|item, _| {
14422                let text = if let Some(description) = item
14423                    .label_details
14424                    .as_ref()
14425                    .and_then(|label_details| label_details.description.as_ref())
14426                {
14427                    format!("{} {}", item.label, description)
14428                } else if let Some(detail) = &item.detail {
14429                    format!("{} {}", item.label, detail)
14430                } else {
14431                    item.label.clone()
14432                };
14433                let len = text.len();
14434                Some(language::CodeLabel {
14435                    text,
14436                    runs: Vec::new(),
14437                    filter_range: 0..len,
14438                })
14439            })),
14440            ..FakeLspAdapter::default()
14441        },
14442    );
14443    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14444    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14445    let worktree_id = workspace
14446        .update(cx, |workspace, _window, cx| {
14447            workspace.project().update(cx, |project, cx| {
14448                project.worktrees(cx).next().unwrap().read(cx).id()
14449            })
14450        })
14451        .unwrap();
14452    let _buffer = project
14453        .update(cx, |project, cx| {
14454            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
14455        })
14456        .await
14457        .unwrap();
14458    let editor = workspace
14459        .update(cx, |workspace, window, cx| {
14460            workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
14461        })
14462        .unwrap()
14463        .await
14464        .unwrap()
14465        .downcast::<Editor>()
14466        .unwrap();
14467    let fake_server = fake_servers.next().await.unwrap();
14468
14469    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
14470    let multiline_label_2 = "a\nb\nc\n";
14471    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
14472    let multiline_description = "d\ne\nf\n";
14473    let multiline_detail_2 = "g\nh\ni\n";
14474
14475    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
14476        move |params, _| async move {
14477            Ok(Some(lsp::CompletionResponse::Array(vec![
14478                lsp::CompletionItem {
14479                    label: multiline_label.to_string(),
14480                    text_edit: gen_text_edit(&params, "new_text_1"),
14481                    ..lsp::CompletionItem::default()
14482                },
14483                lsp::CompletionItem {
14484                    label: "single line label 1".to_string(),
14485                    detail: Some(multiline_detail.to_string()),
14486                    text_edit: gen_text_edit(&params, "new_text_2"),
14487                    ..lsp::CompletionItem::default()
14488                },
14489                lsp::CompletionItem {
14490                    label: "single line label 2".to_string(),
14491                    label_details: Some(lsp::CompletionItemLabelDetails {
14492                        description: Some(multiline_description.to_string()),
14493                        detail: None,
14494                    }),
14495                    text_edit: gen_text_edit(&params, "new_text_2"),
14496                    ..lsp::CompletionItem::default()
14497                },
14498                lsp::CompletionItem {
14499                    label: multiline_label_2.to_string(),
14500                    detail: Some(multiline_detail_2.to_string()),
14501                    text_edit: gen_text_edit(&params, "new_text_3"),
14502                    ..lsp::CompletionItem::default()
14503                },
14504                lsp::CompletionItem {
14505                    label: "Label with many     spaces and \t but without newlines".to_string(),
14506                    detail: Some(
14507                        "Details with many     spaces and \t but without newlines".to_string(),
14508                    ),
14509                    text_edit: gen_text_edit(&params, "new_text_4"),
14510                    ..lsp::CompletionItem::default()
14511                },
14512            ])))
14513        },
14514    );
14515
14516    editor.update_in(cx, |editor, window, cx| {
14517        cx.focus_self(window);
14518        editor.move_to_end(&MoveToEnd, window, cx);
14519        editor.handle_input(".", window, cx);
14520    });
14521    cx.run_until_parked();
14522    completion_handle.next().await.unwrap();
14523
14524    editor.update(cx, |editor, _| {
14525        assert!(editor.context_menu_visible());
14526        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14527        {
14528            let completion_labels = menu
14529                .completions
14530                .borrow()
14531                .iter()
14532                .map(|c| c.label.text.clone())
14533                .collect::<Vec<_>>();
14534            assert_eq!(
14535                completion_labels,
14536                &[
14537                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
14538                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
14539                    "single line label 2 d e f ",
14540                    "a b c g h i ",
14541                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
14542                ],
14543                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
14544            );
14545
14546            for completion in menu
14547                .completions
14548                .borrow()
14549                .iter() {
14550                    assert_eq!(
14551                        completion.label.filter_range,
14552                        0..completion.label.text.len(),
14553                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
14554                    );
14555                }
14556        } else {
14557            panic!("expected completion menu to be open");
14558        }
14559    });
14560}
14561
14562#[gpui::test]
14563async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
14564    init_test(cx, |_| {});
14565    let mut cx = EditorLspTestContext::new_rust(
14566        lsp::ServerCapabilities {
14567            completion_provider: Some(lsp::CompletionOptions {
14568                trigger_characters: Some(vec![".".to_string()]),
14569                ..Default::default()
14570            }),
14571            ..Default::default()
14572        },
14573        cx,
14574    )
14575    .await;
14576    cx.lsp
14577        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14578            Ok(Some(lsp::CompletionResponse::Array(vec![
14579                lsp::CompletionItem {
14580                    label: "first".into(),
14581                    ..Default::default()
14582                },
14583                lsp::CompletionItem {
14584                    label: "last".into(),
14585                    ..Default::default()
14586                },
14587            ])))
14588        });
14589    cx.set_state("variableˇ");
14590    cx.simulate_keystroke(".");
14591    cx.executor().run_until_parked();
14592
14593    cx.update_editor(|editor, _, _| {
14594        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14595        {
14596            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
14597        } else {
14598            panic!("expected completion menu to be open");
14599        }
14600    });
14601
14602    cx.update_editor(|editor, window, cx| {
14603        editor.move_page_down(&MovePageDown::default(), window, cx);
14604        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14605        {
14606            assert!(
14607                menu.selected_item == 1,
14608                "expected PageDown to select the last item from the context menu"
14609            );
14610        } else {
14611            panic!("expected completion menu to stay open after PageDown");
14612        }
14613    });
14614
14615    cx.update_editor(|editor, window, cx| {
14616        editor.move_page_up(&MovePageUp::default(), window, cx);
14617        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14618        {
14619            assert!(
14620                menu.selected_item == 0,
14621                "expected PageUp to select the first item from the context menu"
14622            );
14623        } else {
14624            panic!("expected completion menu to stay open after PageUp");
14625        }
14626    });
14627}
14628
14629#[gpui::test]
14630async fn test_as_is_completions(cx: &mut TestAppContext) {
14631    init_test(cx, |_| {});
14632    let mut cx = EditorLspTestContext::new_rust(
14633        lsp::ServerCapabilities {
14634            completion_provider: Some(lsp::CompletionOptions {
14635                ..Default::default()
14636            }),
14637            ..Default::default()
14638        },
14639        cx,
14640    )
14641    .await;
14642    cx.lsp
14643        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14644            Ok(Some(lsp::CompletionResponse::Array(vec![
14645                lsp::CompletionItem {
14646                    label: "unsafe".into(),
14647                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14648                        range: lsp::Range {
14649                            start: lsp::Position {
14650                                line: 1,
14651                                character: 2,
14652                            },
14653                            end: lsp::Position {
14654                                line: 1,
14655                                character: 3,
14656                            },
14657                        },
14658                        new_text: "unsafe".to_string(),
14659                    })),
14660                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
14661                    ..Default::default()
14662                },
14663            ])))
14664        });
14665    cx.set_state("fn a() {}\n");
14666    cx.executor().run_until_parked();
14667    cx.update_editor(|editor, window, cx| {
14668        editor.show_completions(
14669            &ShowCompletions {
14670                trigger: Some("\n".into()),
14671            },
14672            window,
14673            cx,
14674        );
14675    });
14676    cx.executor().run_until_parked();
14677
14678    cx.update_editor(|editor, window, cx| {
14679        editor.confirm_completion(&Default::default(), window, cx)
14680    });
14681    cx.executor().run_until_parked();
14682    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
14683}
14684
14685#[gpui::test]
14686async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
14687    init_test(cx, |_| {});
14688    let language =
14689        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
14690    let mut cx = EditorLspTestContext::new(
14691        language,
14692        lsp::ServerCapabilities {
14693            completion_provider: Some(lsp::CompletionOptions {
14694                ..lsp::CompletionOptions::default()
14695            }),
14696            ..lsp::ServerCapabilities::default()
14697        },
14698        cx,
14699    )
14700    .await;
14701
14702    cx.set_state(
14703        "#ifndef BAR_H
14704#define BAR_H
14705
14706#include <stdbool.h>
14707
14708int fn_branch(bool do_branch1, bool do_branch2);
14709
14710#endif // BAR_H
14711ˇ",
14712    );
14713    cx.executor().run_until_parked();
14714    cx.update_editor(|editor, window, cx| {
14715        editor.handle_input("#", window, cx);
14716    });
14717    cx.executor().run_until_parked();
14718    cx.update_editor(|editor, window, cx| {
14719        editor.handle_input("i", window, cx);
14720    });
14721    cx.executor().run_until_parked();
14722    cx.update_editor(|editor, window, cx| {
14723        editor.handle_input("n", window, cx);
14724    });
14725    cx.executor().run_until_parked();
14726    cx.assert_editor_state(
14727        "#ifndef BAR_H
14728#define BAR_H
14729
14730#include <stdbool.h>
14731
14732int fn_branch(bool do_branch1, bool do_branch2);
14733
14734#endif // BAR_H
14735#inˇ",
14736    );
14737
14738    cx.lsp
14739        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14740            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
14741                is_incomplete: false,
14742                item_defaults: None,
14743                items: vec![lsp::CompletionItem {
14744                    kind: Some(lsp::CompletionItemKind::SNIPPET),
14745                    label_details: Some(lsp::CompletionItemLabelDetails {
14746                        detail: Some("header".to_string()),
14747                        description: None,
14748                    }),
14749                    label: " include".to_string(),
14750                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14751                        range: lsp::Range {
14752                            start: lsp::Position {
14753                                line: 8,
14754                                character: 1,
14755                            },
14756                            end: lsp::Position {
14757                                line: 8,
14758                                character: 1,
14759                            },
14760                        },
14761                        new_text: "include \"$0\"".to_string(),
14762                    })),
14763                    sort_text: Some("40b67681include".to_string()),
14764                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
14765                    filter_text: Some("include".to_string()),
14766                    insert_text: Some("include \"$0\"".to_string()),
14767                    ..lsp::CompletionItem::default()
14768                }],
14769            })))
14770        });
14771    cx.update_editor(|editor, window, cx| {
14772        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14773    });
14774    cx.executor().run_until_parked();
14775    cx.update_editor(|editor, window, cx| {
14776        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
14777    });
14778    cx.executor().run_until_parked();
14779    cx.assert_editor_state(
14780        "#ifndef BAR_H
14781#define BAR_H
14782
14783#include <stdbool.h>
14784
14785int fn_branch(bool do_branch1, bool do_branch2);
14786
14787#endif // BAR_H
14788#include \"ˇ\"",
14789    );
14790
14791    cx.lsp
14792        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14793            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
14794                is_incomplete: true,
14795                item_defaults: None,
14796                items: vec![lsp::CompletionItem {
14797                    kind: Some(lsp::CompletionItemKind::FILE),
14798                    label: "AGL/".to_string(),
14799                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14800                        range: lsp::Range {
14801                            start: lsp::Position {
14802                                line: 8,
14803                                character: 10,
14804                            },
14805                            end: lsp::Position {
14806                                line: 8,
14807                                character: 11,
14808                            },
14809                        },
14810                        new_text: "AGL/".to_string(),
14811                    })),
14812                    sort_text: Some("40b67681AGL/".to_string()),
14813                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
14814                    filter_text: Some("AGL/".to_string()),
14815                    insert_text: Some("AGL/".to_string()),
14816                    ..lsp::CompletionItem::default()
14817                }],
14818            })))
14819        });
14820    cx.update_editor(|editor, window, cx| {
14821        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14822    });
14823    cx.executor().run_until_parked();
14824    cx.update_editor(|editor, window, cx| {
14825        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
14826    });
14827    cx.executor().run_until_parked();
14828    cx.assert_editor_state(
14829        r##"#ifndef BAR_H
14830#define BAR_H
14831
14832#include <stdbool.h>
14833
14834int fn_branch(bool do_branch1, bool do_branch2);
14835
14836#endif // BAR_H
14837#include "AGL/ˇ"##,
14838    );
14839
14840    cx.update_editor(|editor, window, cx| {
14841        editor.handle_input("\"", window, cx);
14842    });
14843    cx.executor().run_until_parked();
14844    cx.assert_editor_state(
14845        r##"#ifndef BAR_H
14846#define BAR_H
14847
14848#include <stdbool.h>
14849
14850int fn_branch(bool do_branch1, bool do_branch2);
14851
14852#endif // BAR_H
14853#include "AGL/"ˇ"##,
14854    );
14855}
14856
14857#[gpui::test]
14858async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
14859    init_test(cx, |_| {});
14860
14861    let mut cx = EditorLspTestContext::new_rust(
14862        lsp::ServerCapabilities {
14863            completion_provider: Some(lsp::CompletionOptions {
14864                trigger_characters: Some(vec![".".to_string()]),
14865                resolve_provider: Some(true),
14866                ..Default::default()
14867            }),
14868            ..Default::default()
14869        },
14870        cx,
14871    )
14872    .await;
14873
14874    cx.set_state("fn main() { let a = 2ˇ; }");
14875    cx.simulate_keystroke(".");
14876    let completion_item = lsp::CompletionItem {
14877        label: "Some".into(),
14878        kind: Some(lsp::CompletionItemKind::SNIPPET),
14879        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
14880        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
14881            kind: lsp::MarkupKind::Markdown,
14882            value: "```rust\nSome(2)\n```".to_string(),
14883        })),
14884        deprecated: Some(false),
14885        sort_text: Some("Some".to_string()),
14886        filter_text: Some("Some".to_string()),
14887        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
14888        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14889            range: lsp::Range {
14890                start: lsp::Position {
14891                    line: 0,
14892                    character: 22,
14893                },
14894                end: lsp::Position {
14895                    line: 0,
14896                    character: 22,
14897                },
14898            },
14899            new_text: "Some(2)".to_string(),
14900        })),
14901        additional_text_edits: Some(vec![lsp::TextEdit {
14902            range: lsp::Range {
14903                start: lsp::Position {
14904                    line: 0,
14905                    character: 20,
14906                },
14907                end: lsp::Position {
14908                    line: 0,
14909                    character: 22,
14910                },
14911            },
14912            new_text: "".to_string(),
14913        }]),
14914        ..Default::default()
14915    };
14916
14917    let closure_completion_item = completion_item.clone();
14918    let counter = Arc::new(AtomicUsize::new(0));
14919    let counter_clone = counter.clone();
14920    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
14921        let task_completion_item = closure_completion_item.clone();
14922        counter_clone.fetch_add(1, atomic::Ordering::Release);
14923        async move {
14924            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
14925                is_incomplete: true,
14926                item_defaults: None,
14927                items: vec![task_completion_item],
14928            })))
14929        }
14930    });
14931
14932    cx.condition(|editor, _| editor.context_menu_visible())
14933        .await;
14934    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
14935    assert!(request.next().await.is_some());
14936    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14937
14938    cx.simulate_keystrokes("S o m");
14939    cx.condition(|editor, _| editor.context_menu_visible())
14940        .await;
14941    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
14942    assert!(request.next().await.is_some());
14943    assert!(request.next().await.is_some());
14944    assert!(request.next().await.is_some());
14945    request.close();
14946    assert!(request.next().await.is_none());
14947    assert_eq!(
14948        counter.load(atomic::Ordering::Acquire),
14949        4,
14950        "With the completions menu open, only one LSP request should happen per input"
14951    );
14952}
14953
14954#[gpui::test]
14955async fn test_toggle_comment(cx: &mut TestAppContext) {
14956    init_test(cx, |_| {});
14957    let mut cx = EditorTestContext::new(cx).await;
14958    let language = Arc::new(Language::new(
14959        LanguageConfig {
14960            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
14961            ..Default::default()
14962        },
14963        Some(tree_sitter_rust::LANGUAGE.into()),
14964    ));
14965    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
14966
14967    // If multiple selections intersect a line, the line is only toggled once.
14968    cx.set_state(indoc! {"
14969        fn a() {
14970            «//b();
14971            ˇ»// «c();
14972            //ˇ»  d();
14973        }
14974    "});
14975
14976    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
14977
14978    cx.assert_editor_state(indoc! {"
14979        fn a() {
14980            «b();
14981            c();
14982            ˇ» d();
14983        }
14984    "});
14985
14986    // The comment prefix is inserted at the same column for every line in a
14987    // selection.
14988    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
14989
14990    cx.assert_editor_state(indoc! {"
14991        fn a() {
14992            // «b();
14993            // c();
14994            ˇ»//  d();
14995        }
14996    "});
14997
14998    // If a selection ends at the beginning of a line, that line is not toggled.
14999    cx.set_selections_state(indoc! {"
15000        fn a() {
15001            // b();
15002            «// c();
15003        ˇ»    //  d();
15004        }
15005    "});
15006
15007    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15008
15009    cx.assert_editor_state(indoc! {"
15010        fn a() {
15011            // b();
15012            «c();
15013        ˇ»    //  d();
15014        }
15015    "});
15016
15017    // If a selection span a single line and is empty, the line is toggled.
15018    cx.set_state(indoc! {"
15019        fn a() {
15020            a();
15021            b();
15022        ˇ
15023        }
15024    "});
15025
15026    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15027
15028    cx.assert_editor_state(indoc! {"
15029        fn a() {
15030            a();
15031            b();
15032        //•ˇ
15033        }
15034    "});
15035
15036    // If a selection span multiple lines, empty lines are not toggled.
15037    cx.set_state(indoc! {"
15038        fn a() {
15039            «a();
15040
15041            c();ˇ»
15042        }
15043    "});
15044
15045    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15046
15047    cx.assert_editor_state(indoc! {"
15048        fn a() {
15049            // «a();
15050
15051            // c();ˇ»
15052        }
15053    "});
15054
15055    // If a selection includes multiple comment prefixes, all lines are uncommented.
15056    cx.set_state(indoc! {"
15057        fn a() {
15058            «// a();
15059            /// b();
15060            //! c();ˇ»
15061        }
15062    "});
15063
15064    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15065
15066    cx.assert_editor_state(indoc! {"
15067        fn a() {
15068            «a();
15069            b();
15070            c();ˇ»
15071        }
15072    "});
15073}
15074
15075#[gpui::test]
15076async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
15077    init_test(cx, |_| {});
15078    let mut cx = EditorTestContext::new(cx).await;
15079    let language = Arc::new(Language::new(
15080        LanguageConfig {
15081            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15082            ..Default::default()
15083        },
15084        Some(tree_sitter_rust::LANGUAGE.into()),
15085    ));
15086    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15087
15088    let toggle_comments = &ToggleComments {
15089        advance_downwards: false,
15090        ignore_indent: true,
15091    };
15092
15093    // If multiple selections intersect a line, the line is only toggled once.
15094    cx.set_state(indoc! {"
15095        fn a() {
15096        //    «b();
15097        //    c();
15098        //    ˇ» d();
15099        }
15100    "});
15101
15102    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15103
15104    cx.assert_editor_state(indoc! {"
15105        fn a() {
15106            «b();
15107            c();
15108            ˇ» d();
15109        }
15110    "});
15111
15112    // The comment prefix is inserted at the beginning of each line
15113    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15114
15115    cx.assert_editor_state(indoc! {"
15116        fn a() {
15117        //    «b();
15118        //    c();
15119        //    ˇ» d();
15120        }
15121    "});
15122
15123    // If a selection ends at the beginning of a line, that line is not toggled.
15124    cx.set_selections_state(indoc! {"
15125        fn a() {
15126        //    b();
15127        //    «c();
15128        ˇ»//     d();
15129        }
15130    "});
15131
15132    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15133
15134    cx.assert_editor_state(indoc! {"
15135        fn a() {
15136        //    b();
15137            «c();
15138        ˇ»//     d();
15139        }
15140    "});
15141
15142    // If a selection span a single line and is empty, the line is toggled.
15143    cx.set_state(indoc! {"
15144        fn a() {
15145            a();
15146            b();
15147        ˇ
15148        }
15149    "});
15150
15151    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15152
15153    cx.assert_editor_state(indoc! {"
15154        fn a() {
15155            a();
15156            b();
15157        //ˇ
15158        }
15159    "});
15160
15161    // If a selection span multiple lines, empty lines are not toggled.
15162    cx.set_state(indoc! {"
15163        fn a() {
15164            «a();
15165
15166            c();ˇ»
15167        }
15168    "});
15169
15170    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15171
15172    cx.assert_editor_state(indoc! {"
15173        fn a() {
15174        //    «a();
15175
15176        //    c();ˇ»
15177        }
15178    "});
15179
15180    // If a selection includes multiple comment prefixes, all lines are uncommented.
15181    cx.set_state(indoc! {"
15182        fn a() {
15183        //    «a();
15184        ///    b();
15185        //!    c();ˇ»
15186        }
15187    "});
15188
15189    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15190
15191    cx.assert_editor_state(indoc! {"
15192        fn a() {
15193            «a();
15194            b();
15195            c();ˇ»
15196        }
15197    "});
15198}
15199
15200#[gpui::test]
15201async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
15202    init_test(cx, |_| {});
15203
15204    let language = Arc::new(Language::new(
15205        LanguageConfig {
15206            line_comments: vec!["// ".into()],
15207            ..Default::default()
15208        },
15209        Some(tree_sitter_rust::LANGUAGE.into()),
15210    ));
15211
15212    let mut cx = EditorTestContext::new(cx).await;
15213
15214    cx.language_registry().add(language.clone());
15215    cx.update_buffer(|buffer, cx| {
15216        buffer.set_language(Some(language), cx);
15217    });
15218
15219    let toggle_comments = &ToggleComments {
15220        advance_downwards: true,
15221        ignore_indent: false,
15222    };
15223
15224    // Single cursor on one line -> advance
15225    // Cursor moves horizontally 3 characters as well on non-blank line
15226    cx.set_state(indoc!(
15227        "fn a() {
15228             ˇdog();
15229             cat();
15230        }"
15231    ));
15232    cx.update_editor(|editor, window, cx| {
15233        editor.toggle_comments(toggle_comments, window, cx);
15234    });
15235    cx.assert_editor_state(indoc!(
15236        "fn a() {
15237             // dog();
15238             catˇ();
15239        }"
15240    ));
15241
15242    // Single selection on one line -> don't advance
15243    cx.set_state(indoc!(
15244        "fn a() {
15245             «dog()ˇ»;
15246             cat();
15247        }"
15248    ));
15249    cx.update_editor(|editor, window, cx| {
15250        editor.toggle_comments(toggle_comments, window, cx);
15251    });
15252    cx.assert_editor_state(indoc!(
15253        "fn a() {
15254             // «dog()ˇ»;
15255             cat();
15256        }"
15257    ));
15258
15259    // Multiple cursors on one line -> advance
15260    cx.set_state(indoc!(
15261        "fn a() {
15262             ˇdˇog();
15263             cat();
15264        }"
15265    ));
15266    cx.update_editor(|editor, window, cx| {
15267        editor.toggle_comments(toggle_comments, window, cx);
15268    });
15269    cx.assert_editor_state(indoc!(
15270        "fn a() {
15271             // dog();
15272             catˇ(ˇ);
15273        }"
15274    ));
15275
15276    // Multiple cursors on one line, with selection -> don't advance
15277    cx.set_state(indoc!(
15278        "fn a() {
15279             ˇdˇog«()ˇ»;
15280             cat();
15281        }"
15282    ));
15283    cx.update_editor(|editor, window, cx| {
15284        editor.toggle_comments(toggle_comments, window, cx);
15285    });
15286    cx.assert_editor_state(indoc!(
15287        "fn a() {
15288             // ˇdˇog«()ˇ»;
15289             cat();
15290        }"
15291    ));
15292
15293    // Single cursor on one line -> advance
15294    // Cursor moves to column 0 on blank line
15295    cx.set_state(indoc!(
15296        "fn a() {
15297             ˇdog();
15298
15299             cat();
15300        }"
15301    ));
15302    cx.update_editor(|editor, window, cx| {
15303        editor.toggle_comments(toggle_comments, window, cx);
15304    });
15305    cx.assert_editor_state(indoc!(
15306        "fn a() {
15307             // dog();
15308        ˇ
15309             cat();
15310        }"
15311    ));
15312
15313    // Single cursor on one line -> advance
15314    // Cursor starts and ends at column 0
15315    cx.set_state(indoc!(
15316        "fn a() {
15317         ˇ    dog();
15318             cat();
15319        }"
15320    ));
15321    cx.update_editor(|editor, window, cx| {
15322        editor.toggle_comments(toggle_comments, window, cx);
15323    });
15324    cx.assert_editor_state(indoc!(
15325        "fn a() {
15326             // dog();
15327         ˇ    cat();
15328        }"
15329    ));
15330}
15331
15332#[gpui::test]
15333async fn test_toggle_block_comment(cx: &mut TestAppContext) {
15334    init_test(cx, |_| {});
15335
15336    let mut cx = EditorTestContext::new(cx).await;
15337
15338    let html_language = Arc::new(
15339        Language::new(
15340            LanguageConfig {
15341                name: "HTML".into(),
15342                block_comment: Some(BlockCommentConfig {
15343                    start: "<!-- ".into(),
15344                    prefix: "".into(),
15345                    end: " -->".into(),
15346                    tab_size: 0,
15347                }),
15348                ..Default::default()
15349            },
15350            Some(tree_sitter_html::LANGUAGE.into()),
15351        )
15352        .with_injection_query(
15353            r#"
15354            (script_element
15355                (raw_text) @injection.content
15356                (#set! injection.language "javascript"))
15357            "#,
15358        )
15359        .unwrap(),
15360    );
15361
15362    let javascript_language = Arc::new(Language::new(
15363        LanguageConfig {
15364            name: "JavaScript".into(),
15365            line_comments: vec!["// ".into()],
15366            ..Default::default()
15367        },
15368        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
15369    ));
15370
15371    cx.language_registry().add(html_language.clone());
15372    cx.language_registry().add(javascript_language);
15373    cx.update_buffer(|buffer, cx| {
15374        buffer.set_language(Some(html_language), cx);
15375    });
15376
15377    // Toggle comments for empty selections
15378    cx.set_state(
15379        &r#"
15380            <p>A</p>ˇ
15381            <p>B</p>ˇ
15382            <p>C</p>ˇ
15383        "#
15384        .unindent(),
15385    );
15386    cx.update_editor(|editor, window, cx| {
15387        editor.toggle_comments(&ToggleComments::default(), window, cx)
15388    });
15389    cx.assert_editor_state(
15390        &r#"
15391            <!-- <p>A</p>ˇ -->
15392            <!-- <p>B</p>ˇ -->
15393            <!-- <p>C</p>ˇ -->
15394        "#
15395        .unindent(),
15396    );
15397    cx.update_editor(|editor, window, cx| {
15398        editor.toggle_comments(&ToggleComments::default(), window, cx)
15399    });
15400    cx.assert_editor_state(
15401        &r#"
15402            <p>A</p>ˇ
15403            <p>B</p>ˇ
15404            <p>C</p>ˇ
15405        "#
15406        .unindent(),
15407    );
15408
15409    // Toggle comments for mixture of empty and non-empty selections, where
15410    // multiple selections occupy a given line.
15411    cx.set_state(
15412        &r#"
15413            <p>A«</p>
15414            <p>ˇ»B</p>ˇ
15415            <p>C«</p>
15416            <p>ˇ»D</p>ˇ
15417        "#
15418        .unindent(),
15419    );
15420
15421    cx.update_editor(|editor, window, cx| {
15422        editor.toggle_comments(&ToggleComments::default(), window, cx)
15423    });
15424    cx.assert_editor_state(
15425        &r#"
15426            <!-- <p>A«</p>
15427            <p>ˇ»B</p>ˇ -->
15428            <!-- <p>C«</p>
15429            <p>ˇ»D</p>ˇ -->
15430        "#
15431        .unindent(),
15432    );
15433    cx.update_editor(|editor, window, cx| {
15434        editor.toggle_comments(&ToggleComments::default(), window, cx)
15435    });
15436    cx.assert_editor_state(
15437        &r#"
15438            <p>A«</p>
15439            <p>ˇ»B</p>ˇ
15440            <p>C«</p>
15441            <p>ˇ»D</p>ˇ
15442        "#
15443        .unindent(),
15444    );
15445
15446    // Toggle comments when different languages are active for different
15447    // selections.
15448    cx.set_state(
15449        &r#"
15450            ˇ<script>
15451                ˇvar x = new Y();
15452            ˇ</script>
15453        "#
15454        .unindent(),
15455    );
15456    cx.executor().run_until_parked();
15457    cx.update_editor(|editor, window, cx| {
15458        editor.toggle_comments(&ToggleComments::default(), window, cx)
15459    });
15460    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
15461    // Uncommenting and commenting from this position brings in even more wrong artifacts.
15462    cx.assert_editor_state(
15463        &r#"
15464            <!-- ˇ<script> -->
15465                // ˇvar x = new Y();
15466            <!-- ˇ</script> -->
15467        "#
15468        .unindent(),
15469    );
15470}
15471
15472#[gpui::test]
15473fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
15474    init_test(cx, |_| {});
15475
15476    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15477    let multibuffer = cx.new(|cx| {
15478        let mut multibuffer = MultiBuffer::new(ReadWrite);
15479        multibuffer.push_excerpts(
15480            buffer.clone(),
15481            [
15482                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
15483                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
15484            ],
15485            cx,
15486        );
15487        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
15488        multibuffer
15489    });
15490
15491    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
15492    editor.update_in(cx, |editor, window, cx| {
15493        assert_eq!(editor.text(cx), "aaaa\nbbbb");
15494        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15495            s.select_ranges([
15496                Point::new(0, 0)..Point::new(0, 0),
15497                Point::new(1, 0)..Point::new(1, 0),
15498            ])
15499        });
15500
15501        editor.handle_input("X", window, cx);
15502        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
15503        assert_eq!(
15504            editor.selections.ranges(cx),
15505            [
15506                Point::new(0, 1)..Point::new(0, 1),
15507                Point::new(1, 1)..Point::new(1, 1),
15508            ]
15509        );
15510
15511        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
15512        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15513            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
15514        });
15515        editor.backspace(&Default::default(), window, cx);
15516        assert_eq!(editor.text(cx), "Xa\nbbb");
15517        assert_eq!(
15518            editor.selections.ranges(cx),
15519            [Point::new(1, 0)..Point::new(1, 0)]
15520        );
15521
15522        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15523            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
15524        });
15525        editor.backspace(&Default::default(), window, cx);
15526        assert_eq!(editor.text(cx), "X\nbb");
15527        assert_eq!(
15528            editor.selections.ranges(cx),
15529            [Point::new(0, 1)..Point::new(0, 1)]
15530        );
15531    });
15532}
15533
15534#[gpui::test]
15535fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
15536    init_test(cx, |_| {});
15537
15538    let markers = vec![('[', ']').into(), ('(', ')').into()];
15539    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
15540        indoc! {"
15541            [aaaa
15542            (bbbb]
15543            cccc)",
15544        },
15545        markers.clone(),
15546    );
15547    let excerpt_ranges = markers.into_iter().map(|marker| {
15548        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
15549        ExcerptRange::new(context)
15550    });
15551    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
15552    let multibuffer = cx.new(|cx| {
15553        let mut multibuffer = MultiBuffer::new(ReadWrite);
15554        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
15555        multibuffer
15556    });
15557
15558    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
15559    editor.update_in(cx, |editor, window, cx| {
15560        let (expected_text, selection_ranges) = marked_text_ranges(
15561            indoc! {"
15562                aaaa
15563                bˇbbb
15564                bˇbbˇb
15565                cccc"
15566            },
15567            true,
15568        );
15569        assert_eq!(editor.text(cx), expected_text);
15570        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15571            s.select_ranges(selection_ranges)
15572        });
15573
15574        editor.handle_input("X", window, cx);
15575
15576        let (expected_text, expected_selections) = marked_text_ranges(
15577            indoc! {"
15578                aaaa
15579                bXˇbbXb
15580                bXˇbbXˇb
15581                cccc"
15582            },
15583            false,
15584        );
15585        assert_eq!(editor.text(cx), expected_text);
15586        assert_eq!(editor.selections.ranges(cx), expected_selections);
15587
15588        editor.newline(&Newline, window, cx);
15589        let (expected_text, expected_selections) = marked_text_ranges(
15590            indoc! {"
15591                aaaa
15592                bX
15593                ˇbbX
15594                b
15595                bX
15596                ˇbbX
15597                ˇb
15598                cccc"
15599            },
15600            false,
15601        );
15602        assert_eq!(editor.text(cx), expected_text);
15603        assert_eq!(editor.selections.ranges(cx), expected_selections);
15604    });
15605}
15606
15607#[gpui::test]
15608fn test_refresh_selections(cx: &mut TestAppContext) {
15609    init_test(cx, |_| {});
15610
15611    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15612    let mut excerpt1_id = None;
15613    let multibuffer = cx.new(|cx| {
15614        let mut multibuffer = MultiBuffer::new(ReadWrite);
15615        excerpt1_id = multibuffer
15616            .push_excerpts(
15617                buffer.clone(),
15618                [
15619                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
15620                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
15621                ],
15622                cx,
15623            )
15624            .into_iter()
15625            .next();
15626        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
15627        multibuffer
15628    });
15629
15630    let editor = cx.add_window(|window, cx| {
15631        let mut editor = build_editor(multibuffer.clone(), window, cx);
15632        let snapshot = editor.snapshot(window, cx);
15633        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15634            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
15635        });
15636        editor.begin_selection(
15637            Point::new(2, 1).to_display_point(&snapshot),
15638            true,
15639            1,
15640            window,
15641            cx,
15642        );
15643        assert_eq!(
15644            editor.selections.ranges(cx),
15645            [
15646                Point::new(1, 3)..Point::new(1, 3),
15647                Point::new(2, 1)..Point::new(2, 1),
15648            ]
15649        );
15650        editor
15651    });
15652
15653    // Refreshing selections is a no-op when excerpts haven't changed.
15654    _ = editor.update(cx, |editor, window, cx| {
15655        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
15656        assert_eq!(
15657            editor.selections.ranges(cx),
15658            [
15659                Point::new(1, 3)..Point::new(1, 3),
15660                Point::new(2, 1)..Point::new(2, 1),
15661            ]
15662        );
15663    });
15664
15665    multibuffer.update(cx, |multibuffer, cx| {
15666        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
15667    });
15668    _ = editor.update(cx, |editor, window, cx| {
15669        // Removing an excerpt causes the first selection to become degenerate.
15670        assert_eq!(
15671            editor.selections.ranges(cx),
15672            [
15673                Point::new(0, 0)..Point::new(0, 0),
15674                Point::new(0, 1)..Point::new(0, 1)
15675            ]
15676        );
15677
15678        // Refreshing selections will relocate the first selection to the original buffer
15679        // location.
15680        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
15681        assert_eq!(
15682            editor.selections.ranges(cx),
15683            [
15684                Point::new(0, 1)..Point::new(0, 1),
15685                Point::new(0, 3)..Point::new(0, 3)
15686            ]
15687        );
15688        assert!(editor.selections.pending_anchor().is_some());
15689    });
15690}
15691
15692#[gpui::test]
15693fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
15694    init_test(cx, |_| {});
15695
15696    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15697    let mut excerpt1_id = None;
15698    let multibuffer = cx.new(|cx| {
15699        let mut multibuffer = MultiBuffer::new(ReadWrite);
15700        excerpt1_id = multibuffer
15701            .push_excerpts(
15702                buffer.clone(),
15703                [
15704                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
15705                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
15706                ],
15707                cx,
15708            )
15709            .into_iter()
15710            .next();
15711        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
15712        multibuffer
15713    });
15714
15715    let editor = cx.add_window(|window, cx| {
15716        let mut editor = build_editor(multibuffer.clone(), window, cx);
15717        let snapshot = editor.snapshot(window, cx);
15718        editor.begin_selection(
15719            Point::new(1, 3).to_display_point(&snapshot),
15720            false,
15721            1,
15722            window,
15723            cx,
15724        );
15725        assert_eq!(
15726            editor.selections.ranges(cx),
15727            [Point::new(1, 3)..Point::new(1, 3)]
15728        );
15729        editor
15730    });
15731
15732    multibuffer.update(cx, |multibuffer, cx| {
15733        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
15734    });
15735    _ = editor.update(cx, |editor, window, cx| {
15736        assert_eq!(
15737            editor.selections.ranges(cx),
15738            [Point::new(0, 0)..Point::new(0, 0)]
15739        );
15740
15741        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
15742        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
15743        assert_eq!(
15744            editor.selections.ranges(cx),
15745            [Point::new(0, 3)..Point::new(0, 3)]
15746        );
15747        assert!(editor.selections.pending_anchor().is_some());
15748    });
15749}
15750
15751#[gpui::test]
15752async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
15753    init_test(cx, |_| {});
15754
15755    let language = Arc::new(
15756        Language::new(
15757            LanguageConfig {
15758                brackets: BracketPairConfig {
15759                    pairs: vec![
15760                        BracketPair {
15761                            start: "{".to_string(),
15762                            end: "}".to_string(),
15763                            close: true,
15764                            surround: true,
15765                            newline: true,
15766                        },
15767                        BracketPair {
15768                            start: "/* ".to_string(),
15769                            end: " */".to_string(),
15770                            close: true,
15771                            surround: true,
15772                            newline: true,
15773                        },
15774                    ],
15775                    ..Default::default()
15776                },
15777                ..Default::default()
15778            },
15779            Some(tree_sitter_rust::LANGUAGE.into()),
15780        )
15781        .with_indents_query("")
15782        .unwrap(),
15783    );
15784
15785    let text = concat!(
15786        "{   }\n",     //
15787        "  x\n",       //
15788        "  /*   */\n", //
15789        "x\n",         //
15790        "{{} }\n",     //
15791    );
15792
15793    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
15794    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
15795    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
15796    editor
15797        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
15798        .await;
15799
15800    editor.update_in(cx, |editor, window, cx| {
15801        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15802            s.select_display_ranges([
15803                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
15804                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
15805                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
15806            ])
15807        });
15808        editor.newline(&Newline, window, cx);
15809
15810        assert_eq!(
15811            editor.buffer().read(cx).read(cx).text(),
15812            concat!(
15813                "{ \n",    // Suppress rustfmt
15814                "\n",      //
15815                "}\n",     //
15816                "  x\n",   //
15817                "  /* \n", //
15818                "  \n",    //
15819                "  */\n",  //
15820                "x\n",     //
15821                "{{} \n",  //
15822                "}\n",     //
15823            )
15824        );
15825    });
15826}
15827
15828#[gpui::test]
15829fn test_highlighted_ranges(cx: &mut TestAppContext) {
15830    init_test(cx, |_| {});
15831
15832    let editor = cx.add_window(|window, cx| {
15833        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
15834        build_editor(buffer, window, cx)
15835    });
15836
15837    _ = editor.update(cx, |editor, window, cx| {
15838        struct Type1;
15839        struct Type2;
15840
15841        let buffer = editor.buffer.read(cx).snapshot(cx);
15842
15843        let anchor_range =
15844            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
15845
15846        editor.highlight_background::<Type1>(
15847            &[
15848                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
15849                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
15850                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
15851                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
15852            ],
15853            |_| Hsla::red(),
15854            cx,
15855        );
15856        editor.highlight_background::<Type2>(
15857            &[
15858                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
15859                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
15860                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
15861                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
15862            ],
15863            |_| Hsla::green(),
15864            cx,
15865        );
15866
15867        let snapshot = editor.snapshot(window, cx);
15868        let highlighted_ranges = editor.sorted_background_highlights_in_range(
15869            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
15870            &snapshot,
15871            cx.theme(),
15872        );
15873        assert_eq!(
15874            highlighted_ranges,
15875            &[
15876                (
15877                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
15878                    Hsla::green(),
15879                ),
15880                (
15881                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
15882                    Hsla::red(),
15883                ),
15884                (
15885                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
15886                    Hsla::green(),
15887                ),
15888                (
15889                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
15890                    Hsla::red(),
15891                ),
15892            ]
15893        );
15894        assert_eq!(
15895            editor.sorted_background_highlights_in_range(
15896                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
15897                &snapshot,
15898                cx.theme(),
15899            ),
15900            &[(
15901                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
15902                Hsla::red(),
15903            )]
15904        );
15905    });
15906}
15907
15908#[gpui::test]
15909async fn test_following(cx: &mut TestAppContext) {
15910    init_test(cx, |_| {});
15911
15912    let fs = FakeFs::new(cx.executor());
15913    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
15914
15915    let buffer = project.update(cx, |project, cx| {
15916        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, false, cx);
15917        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
15918    });
15919    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
15920    let follower = cx.update(|cx| {
15921        cx.open_window(
15922            WindowOptions {
15923                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
15924                    gpui::Point::new(px(0.), px(0.)),
15925                    gpui::Point::new(px(10.), px(80.)),
15926                ))),
15927                ..Default::default()
15928            },
15929            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
15930        )
15931        .unwrap()
15932    });
15933
15934    let is_still_following = Rc::new(RefCell::new(true));
15935    let follower_edit_event_count = Rc::new(RefCell::new(0));
15936    let pending_update = Rc::new(RefCell::new(None));
15937    let leader_entity = leader.root(cx).unwrap();
15938    let follower_entity = follower.root(cx).unwrap();
15939    _ = follower.update(cx, {
15940        let update = pending_update.clone();
15941        let is_still_following = is_still_following.clone();
15942        let follower_edit_event_count = follower_edit_event_count.clone();
15943        |_, window, cx| {
15944            cx.subscribe_in(
15945                &leader_entity,
15946                window,
15947                move |_, leader, event, window, cx| {
15948                    leader.read(cx).add_event_to_update_proto(
15949                        event,
15950                        &mut update.borrow_mut(),
15951                        window,
15952                        cx,
15953                    );
15954                },
15955            )
15956            .detach();
15957
15958            cx.subscribe_in(
15959                &follower_entity,
15960                window,
15961                move |_, _, event: &EditorEvent, _window, _cx| {
15962                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
15963                        *is_still_following.borrow_mut() = false;
15964                    }
15965
15966                    if let EditorEvent::BufferEdited = event {
15967                        *follower_edit_event_count.borrow_mut() += 1;
15968                    }
15969                },
15970            )
15971            .detach();
15972        }
15973    });
15974
15975    // Update the selections only
15976    _ = leader.update(cx, |leader, window, cx| {
15977        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15978            s.select_ranges([1..1])
15979        });
15980    });
15981    follower
15982        .update(cx, |follower, window, cx| {
15983            follower.apply_update_proto(
15984                &project,
15985                pending_update.borrow_mut().take().unwrap(),
15986                window,
15987                cx,
15988            )
15989        })
15990        .unwrap()
15991        .await
15992        .unwrap();
15993    _ = follower.update(cx, |follower, _, cx| {
15994        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
15995    });
15996    assert!(*is_still_following.borrow());
15997    assert_eq!(*follower_edit_event_count.borrow(), 0);
15998
15999    // Update the scroll position only
16000    _ = leader.update(cx, |leader, window, cx| {
16001        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16002    });
16003    follower
16004        .update(cx, |follower, window, cx| {
16005            follower.apply_update_proto(
16006                &project,
16007                pending_update.borrow_mut().take().unwrap(),
16008                window,
16009                cx,
16010            )
16011        })
16012        .unwrap()
16013        .await
16014        .unwrap();
16015    assert_eq!(
16016        follower
16017            .update(cx, |follower, _, cx| follower.scroll_position(cx))
16018            .unwrap(),
16019        gpui::Point::new(1.5, 3.5)
16020    );
16021    assert!(*is_still_following.borrow());
16022    assert_eq!(*follower_edit_event_count.borrow(), 0);
16023
16024    // Update the selections and scroll position. The follower's scroll position is updated
16025    // via autoscroll, not via the leader's exact scroll position.
16026    _ = leader.update(cx, |leader, window, cx| {
16027        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16028            s.select_ranges([0..0])
16029        });
16030        leader.request_autoscroll(Autoscroll::newest(), cx);
16031        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16032    });
16033    follower
16034        .update(cx, |follower, window, cx| {
16035            follower.apply_update_proto(
16036                &project,
16037                pending_update.borrow_mut().take().unwrap(),
16038                window,
16039                cx,
16040            )
16041        })
16042        .unwrap()
16043        .await
16044        .unwrap();
16045    _ = follower.update(cx, |follower, _, cx| {
16046        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
16047        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
16048    });
16049    assert!(*is_still_following.borrow());
16050
16051    // Creating a pending selection that precedes another selection
16052    _ = leader.update(cx, |leader, window, cx| {
16053        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16054            s.select_ranges([1..1])
16055        });
16056        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
16057    });
16058    follower
16059        .update(cx, |follower, window, cx| {
16060            follower.apply_update_proto(
16061                &project,
16062                pending_update.borrow_mut().take().unwrap(),
16063                window,
16064                cx,
16065            )
16066        })
16067        .unwrap()
16068        .await
16069        .unwrap();
16070    _ = follower.update(cx, |follower, _, cx| {
16071        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
16072    });
16073    assert!(*is_still_following.borrow());
16074
16075    // Extend the pending selection so that it surrounds another selection
16076    _ = leader.update(cx, |leader, window, cx| {
16077        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
16078    });
16079    follower
16080        .update(cx, |follower, window, cx| {
16081            follower.apply_update_proto(
16082                &project,
16083                pending_update.borrow_mut().take().unwrap(),
16084                window,
16085                cx,
16086            )
16087        })
16088        .unwrap()
16089        .await
16090        .unwrap();
16091    _ = follower.update(cx, |follower, _, cx| {
16092        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
16093    });
16094
16095    // Scrolling locally breaks the follow
16096    _ = follower.update(cx, |follower, window, cx| {
16097        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
16098        follower.set_scroll_anchor(
16099            ScrollAnchor {
16100                anchor: top_anchor,
16101                offset: gpui::Point::new(0.0, 0.5),
16102            },
16103            window,
16104            cx,
16105        );
16106    });
16107    assert!(!(*is_still_following.borrow()));
16108}
16109
16110#[gpui::test]
16111async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
16112    init_test(cx, |_| {});
16113
16114    let fs = FakeFs::new(cx.executor());
16115    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16116    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16117    let pane = workspace
16118        .update(cx, |workspace, _, _| workspace.active_pane().clone())
16119        .unwrap();
16120
16121    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16122
16123    let leader = pane.update_in(cx, |_, window, cx| {
16124        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
16125        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
16126    });
16127
16128    // Start following the editor when it has no excerpts.
16129    let mut state_message =
16130        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16131    let workspace_entity = workspace.root(cx).unwrap();
16132    let follower_1 = cx
16133        .update_window(*workspace.deref(), |_, window, cx| {
16134            Editor::from_state_proto(
16135                workspace_entity,
16136                ViewId {
16137                    creator: CollaboratorId::PeerId(PeerId::default()),
16138                    id: 0,
16139                },
16140                &mut state_message,
16141                window,
16142                cx,
16143            )
16144        })
16145        .unwrap()
16146        .unwrap()
16147        .await
16148        .unwrap();
16149
16150    let update_message = Rc::new(RefCell::new(None));
16151    follower_1.update_in(cx, {
16152        let update = update_message.clone();
16153        |_, window, cx| {
16154            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
16155                leader.read(cx).add_event_to_update_proto(
16156                    event,
16157                    &mut update.borrow_mut(),
16158                    window,
16159                    cx,
16160                );
16161            })
16162            .detach();
16163        }
16164    });
16165
16166    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
16167        (
16168            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, false, cx),
16169            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, false, cx),
16170        )
16171    });
16172
16173    // Insert some excerpts.
16174    leader.update(cx, |leader, cx| {
16175        leader.buffer.update(cx, |multibuffer, cx| {
16176            multibuffer.set_excerpts_for_path(
16177                PathKey::namespaced(1, Arc::from(Path::new("b.txt"))),
16178                buffer_1.clone(),
16179                vec![
16180                    Point::row_range(0..3),
16181                    Point::row_range(1..6),
16182                    Point::row_range(12..15),
16183                ],
16184                0,
16185                cx,
16186            );
16187            multibuffer.set_excerpts_for_path(
16188                PathKey::namespaced(1, Arc::from(Path::new("a.txt"))),
16189                buffer_2.clone(),
16190                vec![Point::row_range(0..6), Point::row_range(8..12)],
16191                0,
16192                cx,
16193            );
16194        });
16195    });
16196
16197    // Apply the update of adding the excerpts.
16198    follower_1
16199        .update_in(cx, |follower, window, cx| {
16200            follower.apply_update_proto(
16201                &project,
16202                update_message.borrow().clone().unwrap(),
16203                window,
16204                cx,
16205            )
16206        })
16207        .await
16208        .unwrap();
16209    assert_eq!(
16210        follower_1.update(cx, |editor, cx| editor.text(cx)),
16211        leader.update(cx, |editor, cx| editor.text(cx))
16212    );
16213    update_message.borrow_mut().take();
16214
16215    // Start following separately after it already has excerpts.
16216    let mut state_message =
16217        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16218    let workspace_entity = workspace.root(cx).unwrap();
16219    let follower_2 = cx
16220        .update_window(*workspace.deref(), |_, window, cx| {
16221            Editor::from_state_proto(
16222                workspace_entity,
16223                ViewId {
16224                    creator: CollaboratorId::PeerId(PeerId::default()),
16225                    id: 0,
16226                },
16227                &mut state_message,
16228                window,
16229                cx,
16230            )
16231        })
16232        .unwrap()
16233        .unwrap()
16234        .await
16235        .unwrap();
16236    assert_eq!(
16237        follower_2.update(cx, |editor, cx| editor.text(cx)),
16238        leader.update(cx, |editor, cx| editor.text(cx))
16239    );
16240
16241    // Remove some excerpts.
16242    leader.update(cx, |leader, cx| {
16243        leader.buffer.update(cx, |multibuffer, cx| {
16244            let excerpt_ids = multibuffer.excerpt_ids();
16245            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
16246            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
16247        });
16248    });
16249
16250    // Apply the update of removing the excerpts.
16251    follower_1
16252        .update_in(cx, |follower, window, cx| {
16253            follower.apply_update_proto(
16254                &project,
16255                update_message.borrow().clone().unwrap(),
16256                window,
16257                cx,
16258            )
16259        })
16260        .await
16261        .unwrap();
16262    follower_2
16263        .update_in(cx, |follower, window, cx| {
16264            follower.apply_update_proto(
16265                &project,
16266                update_message.borrow().clone().unwrap(),
16267                window,
16268                cx,
16269            )
16270        })
16271        .await
16272        .unwrap();
16273    update_message.borrow_mut().take();
16274    assert_eq!(
16275        follower_1.update(cx, |editor, cx| editor.text(cx)),
16276        leader.update(cx, |editor, cx| editor.text(cx))
16277    );
16278}
16279
16280#[gpui::test]
16281async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16282    init_test(cx, |_| {});
16283
16284    let mut cx = EditorTestContext::new(cx).await;
16285    let lsp_store =
16286        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
16287
16288    cx.set_state(indoc! {"
16289        ˇfn func(abc def: i32) -> u32 {
16290        }
16291    "});
16292
16293    cx.update(|_, cx| {
16294        lsp_store.update(cx, |lsp_store, cx| {
16295            lsp_store
16296                .update_diagnostics(
16297                    LanguageServerId(0),
16298                    lsp::PublishDiagnosticsParams {
16299                        uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
16300                        version: None,
16301                        diagnostics: vec![
16302                            lsp::Diagnostic {
16303                                range: lsp::Range::new(
16304                                    lsp::Position::new(0, 11),
16305                                    lsp::Position::new(0, 12),
16306                                ),
16307                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16308                                ..Default::default()
16309                            },
16310                            lsp::Diagnostic {
16311                                range: lsp::Range::new(
16312                                    lsp::Position::new(0, 12),
16313                                    lsp::Position::new(0, 15),
16314                                ),
16315                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16316                                ..Default::default()
16317                            },
16318                            lsp::Diagnostic {
16319                                range: lsp::Range::new(
16320                                    lsp::Position::new(0, 25),
16321                                    lsp::Position::new(0, 28),
16322                                ),
16323                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16324                                ..Default::default()
16325                            },
16326                        ],
16327                    },
16328                    None,
16329                    DiagnosticSourceKind::Pushed,
16330                    &[],
16331                    cx,
16332                )
16333                .unwrap()
16334        });
16335    });
16336
16337    executor.run_until_parked();
16338
16339    cx.update_editor(|editor, window, cx| {
16340        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16341    });
16342
16343    cx.assert_editor_state(indoc! {"
16344        fn func(abc def: i32) -> ˇu32 {
16345        }
16346    "});
16347
16348    cx.update_editor(|editor, window, cx| {
16349        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16350    });
16351
16352    cx.assert_editor_state(indoc! {"
16353        fn func(abc ˇdef: i32) -> u32 {
16354        }
16355    "});
16356
16357    cx.update_editor(|editor, window, cx| {
16358        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16359    });
16360
16361    cx.assert_editor_state(indoc! {"
16362        fn func(abcˇ def: i32) -> u32 {
16363        }
16364    "});
16365
16366    cx.update_editor(|editor, window, cx| {
16367        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16368    });
16369
16370    cx.assert_editor_state(indoc! {"
16371        fn func(abc def: i32) -> ˇu32 {
16372        }
16373    "});
16374}
16375
16376#[gpui::test]
16377async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16378    init_test(cx, |_| {});
16379
16380    let mut cx = EditorTestContext::new(cx).await;
16381
16382    let diff_base = r#"
16383        use some::mod;
16384
16385        const A: u32 = 42;
16386
16387        fn main() {
16388            println!("hello");
16389
16390            println!("world");
16391        }
16392        "#
16393    .unindent();
16394
16395    // Edits are modified, removed, modified, added
16396    cx.set_state(
16397        &r#"
16398        use some::modified;
16399
16400        ˇ
16401        fn main() {
16402            println!("hello there");
16403
16404            println!("around the");
16405            println!("world");
16406        }
16407        "#
16408        .unindent(),
16409    );
16410
16411    cx.set_head_text(&diff_base);
16412    executor.run_until_parked();
16413
16414    cx.update_editor(|editor, window, cx| {
16415        //Wrap around the bottom of the buffer
16416        for _ in 0..3 {
16417            editor.go_to_next_hunk(&GoToHunk, window, cx);
16418        }
16419    });
16420
16421    cx.assert_editor_state(
16422        &r#"
16423        ˇuse some::modified;
16424
16425
16426        fn main() {
16427            println!("hello there");
16428
16429            println!("around the");
16430            println!("world");
16431        }
16432        "#
16433        .unindent(),
16434    );
16435
16436    cx.update_editor(|editor, window, cx| {
16437        //Wrap around the top of the buffer
16438        for _ in 0..2 {
16439            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16440        }
16441    });
16442
16443    cx.assert_editor_state(
16444        &r#"
16445        use some::modified;
16446
16447
16448        fn main() {
16449        ˇ    println!("hello there");
16450
16451            println!("around the");
16452            println!("world");
16453        }
16454        "#
16455        .unindent(),
16456    );
16457
16458    cx.update_editor(|editor, window, cx| {
16459        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16460    });
16461
16462    cx.assert_editor_state(
16463        &r#"
16464        use some::modified;
16465
16466        ˇ
16467        fn main() {
16468            println!("hello there");
16469
16470            println!("around the");
16471            println!("world");
16472        }
16473        "#
16474        .unindent(),
16475    );
16476
16477    cx.update_editor(|editor, window, cx| {
16478        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16479    });
16480
16481    cx.assert_editor_state(
16482        &r#"
16483        ˇuse some::modified;
16484
16485
16486        fn main() {
16487            println!("hello there");
16488
16489            println!("around the");
16490            println!("world");
16491        }
16492        "#
16493        .unindent(),
16494    );
16495
16496    cx.update_editor(|editor, window, cx| {
16497        for _ in 0..2 {
16498            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16499        }
16500    });
16501
16502    cx.assert_editor_state(
16503        &r#"
16504        use some::modified;
16505
16506
16507        fn main() {
16508        ˇ    println!("hello there");
16509
16510            println!("around the");
16511            println!("world");
16512        }
16513        "#
16514        .unindent(),
16515    );
16516
16517    cx.update_editor(|editor, window, cx| {
16518        editor.fold(&Fold, window, cx);
16519    });
16520
16521    cx.update_editor(|editor, window, cx| {
16522        editor.go_to_next_hunk(&GoToHunk, window, cx);
16523    });
16524
16525    cx.assert_editor_state(
16526        &r#"
16527        ˇuse some::modified;
16528
16529
16530        fn main() {
16531            println!("hello there");
16532
16533            println!("around the");
16534            println!("world");
16535        }
16536        "#
16537        .unindent(),
16538    );
16539}
16540
16541#[test]
16542fn test_split_words() {
16543    fn split(text: &str) -> Vec<&str> {
16544        split_words(text).collect()
16545    }
16546
16547    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
16548    assert_eq!(split("hello_world"), &["hello_", "world"]);
16549    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
16550    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
16551    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
16552    assert_eq!(split("helloworld"), &["helloworld"]);
16553
16554    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
16555}
16556
16557#[gpui::test]
16558async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
16559    init_test(cx, |_| {});
16560
16561    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
16562    let mut assert = |before, after| {
16563        let _state_context = cx.set_state(before);
16564        cx.run_until_parked();
16565        cx.update_editor(|editor, window, cx| {
16566            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
16567        });
16568        cx.run_until_parked();
16569        cx.assert_editor_state(after);
16570    };
16571
16572    // Outside bracket jumps to outside of matching bracket
16573    assert("console.logˇ(var);", "console.log(var)ˇ;");
16574    assert("console.log(var)ˇ;", "console.logˇ(var);");
16575
16576    // Inside bracket jumps to inside of matching bracket
16577    assert("console.log(ˇvar);", "console.log(varˇ);");
16578    assert("console.log(varˇ);", "console.log(ˇvar);");
16579
16580    // When outside a bracket and inside, favor jumping to the inside bracket
16581    assert(
16582        "console.log('foo', [1, 2, 3]ˇ);",
16583        "console.log(ˇ'foo', [1, 2, 3]);",
16584    );
16585    assert(
16586        "console.log(ˇ'foo', [1, 2, 3]);",
16587        "console.log('foo', [1, 2, 3]ˇ);",
16588    );
16589
16590    // Bias forward if two options are equally likely
16591    assert(
16592        "let result = curried_fun()ˇ();",
16593        "let result = curried_fun()()ˇ;",
16594    );
16595
16596    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
16597    assert(
16598        indoc! {"
16599            function test() {
16600                console.log('test')ˇ
16601            }"},
16602        indoc! {"
16603            function test() {
16604                console.logˇ('test')
16605            }"},
16606    );
16607}
16608
16609#[gpui::test]
16610async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
16611    init_test(cx, |_| {});
16612
16613    let fs = FakeFs::new(cx.executor());
16614    fs.insert_tree(
16615        path!("/a"),
16616        json!({
16617            "main.rs": "fn main() { let a = 5; }",
16618            "other.rs": "// Test file",
16619        }),
16620    )
16621    .await;
16622    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16623
16624    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
16625    language_registry.add(Arc::new(Language::new(
16626        LanguageConfig {
16627            name: "Rust".into(),
16628            matcher: LanguageMatcher {
16629                path_suffixes: vec!["rs".to_string()],
16630                ..Default::default()
16631            },
16632            brackets: BracketPairConfig {
16633                pairs: vec![BracketPair {
16634                    start: "{".to_string(),
16635                    end: "}".to_string(),
16636                    close: true,
16637                    surround: true,
16638                    newline: true,
16639                }],
16640                disabled_scopes_by_bracket_ix: Vec::new(),
16641            },
16642            ..Default::default()
16643        },
16644        Some(tree_sitter_rust::LANGUAGE.into()),
16645    )));
16646    let mut fake_servers = language_registry.register_fake_lsp(
16647        "Rust",
16648        FakeLspAdapter {
16649            capabilities: lsp::ServerCapabilities {
16650                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
16651                    first_trigger_character: "{".to_string(),
16652                    more_trigger_character: None,
16653                }),
16654                ..Default::default()
16655            },
16656            ..Default::default()
16657        },
16658    );
16659
16660    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16661
16662    let cx = &mut VisualTestContext::from_window(*workspace, cx);
16663
16664    let worktree_id = workspace
16665        .update(cx, |workspace, _, cx| {
16666            workspace.project().update(cx, |project, cx| {
16667                project.worktrees(cx).next().unwrap().read(cx).id()
16668            })
16669        })
16670        .unwrap();
16671
16672    let buffer = project
16673        .update(cx, |project, cx| {
16674            project.open_local_buffer(path!("/a/main.rs"), cx)
16675        })
16676        .await
16677        .unwrap();
16678    let editor_handle = workspace
16679        .update(cx, |workspace, window, cx| {
16680            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
16681        })
16682        .unwrap()
16683        .await
16684        .unwrap()
16685        .downcast::<Editor>()
16686        .unwrap();
16687
16688    cx.executor().start_waiting();
16689    let fake_server = fake_servers.next().await.unwrap();
16690
16691    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
16692        |params, _| async move {
16693            assert_eq!(
16694                params.text_document_position.text_document.uri,
16695                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
16696            );
16697            assert_eq!(
16698                params.text_document_position.position,
16699                lsp::Position::new(0, 21),
16700            );
16701
16702            Ok(Some(vec![lsp::TextEdit {
16703                new_text: "]".to_string(),
16704                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
16705            }]))
16706        },
16707    );
16708
16709    editor_handle.update_in(cx, |editor, window, cx| {
16710        window.focus(&editor.focus_handle(cx));
16711        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16712            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
16713        });
16714        editor.handle_input("{", window, cx);
16715    });
16716
16717    cx.executor().run_until_parked();
16718
16719    buffer.update(cx, |buffer, _| {
16720        assert_eq!(
16721            buffer.text(),
16722            "fn main() { let a = {5}; }",
16723            "No extra braces from on type formatting should appear in the buffer"
16724        )
16725    });
16726}
16727
16728#[gpui::test(iterations = 20, seeds(31))]
16729async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
16730    init_test(cx, |_| {});
16731
16732    let mut cx = EditorLspTestContext::new_rust(
16733        lsp::ServerCapabilities {
16734            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
16735                first_trigger_character: ".".to_string(),
16736                more_trigger_character: None,
16737            }),
16738            ..Default::default()
16739        },
16740        cx,
16741    )
16742    .await;
16743
16744    cx.update_buffer(|buffer, _| {
16745        // This causes autoindent to be async.
16746        buffer.set_sync_parse_timeout(Duration::ZERO)
16747    });
16748
16749    cx.set_state("fn c() {\n    d()ˇ\n}\n");
16750    cx.simulate_keystroke("\n");
16751    cx.run_until_parked();
16752
16753    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
16754    let mut request =
16755        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
16756            let buffer_cloned = buffer_cloned.clone();
16757            async move {
16758                buffer_cloned.update(&mut cx, |buffer, _| {
16759                    assert_eq!(
16760                        buffer.text(),
16761                        "fn c() {\n    d()\n        .\n}\n",
16762                        "OnTypeFormatting should triggered after autoindent applied"
16763                    )
16764                })?;
16765
16766                Ok(Some(vec![]))
16767            }
16768        });
16769
16770    cx.simulate_keystroke(".");
16771    cx.run_until_parked();
16772
16773    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
16774    assert!(request.next().await.is_some());
16775    request.close();
16776    assert!(request.next().await.is_none());
16777}
16778
16779#[gpui::test]
16780async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
16781    init_test(cx, |_| {});
16782
16783    let fs = FakeFs::new(cx.executor());
16784    fs.insert_tree(
16785        path!("/a"),
16786        json!({
16787            "main.rs": "fn main() { let a = 5; }",
16788            "other.rs": "// Test file",
16789        }),
16790    )
16791    .await;
16792
16793    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16794
16795    let server_restarts = Arc::new(AtomicUsize::new(0));
16796    let closure_restarts = Arc::clone(&server_restarts);
16797    let language_server_name = "test language server";
16798    let language_name: LanguageName = "Rust".into();
16799
16800    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
16801    language_registry.add(Arc::new(Language::new(
16802        LanguageConfig {
16803            name: language_name.clone(),
16804            matcher: LanguageMatcher {
16805                path_suffixes: vec!["rs".to_string()],
16806                ..Default::default()
16807            },
16808            ..Default::default()
16809        },
16810        Some(tree_sitter_rust::LANGUAGE.into()),
16811    )));
16812    let mut fake_servers = language_registry.register_fake_lsp(
16813        "Rust",
16814        FakeLspAdapter {
16815            name: language_server_name,
16816            initialization_options: Some(json!({
16817                "testOptionValue": true
16818            })),
16819            initializer: Some(Box::new(move |fake_server| {
16820                let task_restarts = Arc::clone(&closure_restarts);
16821                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
16822                    task_restarts.fetch_add(1, atomic::Ordering::Release);
16823                    futures::future::ready(Ok(()))
16824                });
16825            })),
16826            ..Default::default()
16827        },
16828    );
16829
16830    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16831    let _buffer = project
16832        .update(cx, |project, cx| {
16833            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
16834        })
16835        .await
16836        .unwrap();
16837    let _fake_server = fake_servers.next().await.unwrap();
16838    update_test_language_settings(cx, |language_settings| {
16839        language_settings.languages.0.insert(
16840            language_name.clone(),
16841            LanguageSettingsContent {
16842                tab_size: NonZeroU32::new(8),
16843                ..Default::default()
16844            },
16845        );
16846    });
16847    cx.executor().run_until_parked();
16848    assert_eq!(
16849        server_restarts.load(atomic::Ordering::Acquire),
16850        0,
16851        "Should not restart LSP server on an unrelated change"
16852    );
16853
16854    update_test_project_settings(cx, |project_settings| {
16855        project_settings.lsp.insert(
16856            "Some other server name".into(),
16857            LspSettings {
16858                binary: None,
16859                settings: None,
16860                initialization_options: Some(json!({
16861                    "some other init value": false
16862                })),
16863                enable_lsp_tasks: false,
16864                fetch: None,
16865            },
16866        );
16867    });
16868    cx.executor().run_until_parked();
16869    assert_eq!(
16870        server_restarts.load(atomic::Ordering::Acquire),
16871        0,
16872        "Should not restart LSP server on an unrelated LSP settings change"
16873    );
16874
16875    update_test_project_settings(cx, |project_settings| {
16876        project_settings.lsp.insert(
16877            language_server_name.into(),
16878            LspSettings {
16879                binary: None,
16880                settings: None,
16881                initialization_options: Some(json!({
16882                    "anotherInitValue": false
16883                })),
16884                enable_lsp_tasks: false,
16885                fetch: None,
16886            },
16887        );
16888    });
16889    cx.executor().run_until_parked();
16890    assert_eq!(
16891        server_restarts.load(atomic::Ordering::Acquire),
16892        1,
16893        "Should restart LSP server on a related LSP settings change"
16894    );
16895
16896    update_test_project_settings(cx, |project_settings| {
16897        project_settings.lsp.insert(
16898            language_server_name.into(),
16899            LspSettings {
16900                binary: None,
16901                settings: None,
16902                initialization_options: Some(json!({
16903                    "anotherInitValue": false
16904                })),
16905                enable_lsp_tasks: false,
16906                fetch: None,
16907            },
16908        );
16909    });
16910    cx.executor().run_until_parked();
16911    assert_eq!(
16912        server_restarts.load(atomic::Ordering::Acquire),
16913        1,
16914        "Should not restart LSP server on a related LSP settings change that is the same"
16915    );
16916
16917    update_test_project_settings(cx, |project_settings| {
16918        project_settings.lsp.insert(
16919            language_server_name.into(),
16920            LspSettings {
16921                binary: None,
16922                settings: None,
16923                initialization_options: None,
16924                enable_lsp_tasks: false,
16925                fetch: None,
16926            },
16927        );
16928    });
16929    cx.executor().run_until_parked();
16930    assert_eq!(
16931        server_restarts.load(atomic::Ordering::Acquire),
16932        2,
16933        "Should restart LSP server on another related LSP settings change"
16934    );
16935}
16936
16937#[gpui::test]
16938async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
16939    init_test(cx, |_| {});
16940
16941    let mut cx = EditorLspTestContext::new_rust(
16942        lsp::ServerCapabilities {
16943            completion_provider: Some(lsp::CompletionOptions {
16944                trigger_characters: Some(vec![".".to_string()]),
16945                resolve_provider: Some(true),
16946                ..Default::default()
16947            }),
16948            ..Default::default()
16949        },
16950        cx,
16951    )
16952    .await;
16953
16954    cx.set_state("fn main() { let a = 2ˇ; }");
16955    cx.simulate_keystroke(".");
16956    let completion_item = lsp::CompletionItem {
16957        label: "some".into(),
16958        kind: Some(lsp::CompletionItemKind::SNIPPET),
16959        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
16960        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
16961            kind: lsp::MarkupKind::Markdown,
16962            value: "```rust\nSome(2)\n```".to_string(),
16963        })),
16964        deprecated: Some(false),
16965        sort_text: Some("fffffff2".to_string()),
16966        filter_text: Some("some".to_string()),
16967        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
16968        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16969            range: lsp::Range {
16970                start: lsp::Position {
16971                    line: 0,
16972                    character: 22,
16973                },
16974                end: lsp::Position {
16975                    line: 0,
16976                    character: 22,
16977                },
16978            },
16979            new_text: "Some(2)".to_string(),
16980        })),
16981        additional_text_edits: Some(vec![lsp::TextEdit {
16982            range: lsp::Range {
16983                start: lsp::Position {
16984                    line: 0,
16985                    character: 20,
16986                },
16987                end: lsp::Position {
16988                    line: 0,
16989                    character: 22,
16990                },
16991            },
16992            new_text: "".to_string(),
16993        }]),
16994        ..Default::default()
16995    };
16996
16997    let closure_completion_item = completion_item.clone();
16998    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
16999        let task_completion_item = closure_completion_item.clone();
17000        async move {
17001            Ok(Some(lsp::CompletionResponse::Array(vec![
17002                task_completion_item,
17003            ])))
17004        }
17005    });
17006
17007    request.next().await;
17008
17009    cx.condition(|editor, _| editor.context_menu_visible())
17010        .await;
17011    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
17012        editor
17013            .confirm_completion(&ConfirmCompletion::default(), window, cx)
17014            .unwrap()
17015    });
17016    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
17017
17018    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
17019        let task_completion_item = completion_item.clone();
17020        async move { Ok(task_completion_item) }
17021    })
17022    .next()
17023    .await
17024    .unwrap();
17025    apply_additional_edits.await.unwrap();
17026    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
17027}
17028
17029#[gpui::test]
17030async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
17031    init_test(cx, |_| {});
17032
17033    let mut cx = EditorLspTestContext::new_rust(
17034        lsp::ServerCapabilities {
17035            completion_provider: Some(lsp::CompletionOptions {
17036                trigger_characters: Some(vec![".".to_string()]),
17037                resolve_provider: Some(true),
17038                ..Default::default()
17039            }),
17040            ..Default::default()
17041        },
17042        cx,
17043    )
17044    .await;
17045
17046    cx.set_state("fn main() { let a = 2ˇ; }");
17047    cx.simulate_keystroke(".");
17048
17049    let item1 = lsp::CompletionItem {
17050        label: "method id()".to_string(),
17051        filter_text: Some("id".to_string()),
17052        detail: None,
17053        documentation: None,
17054        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17055            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17056            new_text: ".id".to_string(),
17057        })),
17058        ..lsp::CompletionItem::default()
17059    };
17060
17061    let item2 = lsp::CompletionItem {
17062        label: "other".to_string(),
17063        filter_text: Some("other".to_string()),
17064        detail: None,
17065        documentation: None,
17066        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17067            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17068            new_text: ".other".to_string(),
17069        })),
17070        ..lsp::CompletionItem::default()
17071    };
17072
17073    let item1 = item1.clone();
17074    cx.set_request_handler::<lsp::request::Completion, _, _>({
17075        let item1 = item1.clone();
17076        move |_, _, _| {
17077            let item1 = item1.clone();
17078            let item2 = item2.clone();
17079            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
17080        }
17081    })
17082    .next()
17083    .await;
17084
17085    cx.condition(|editor, _| editor.context_menu_visible())
17086        .await;
17087    cx.update_editor(|editor, _, _| {
17088        let context_menu = editor.context_menu.borrow_mut();
17089        let context_menu = context_menu
17090            .as_ref()
17091            .expect("Should have the context menu deployed");
17092        match context_menu {
17093            CodeContextMenu::Completions(completions_menu) => {
17094                let completions = completions_menu.completions.borrow_mut();
17095                assert_eq!(
17096                    completions
17097                        .iter()
17098                        .map(|completion| &completion.label.text)
17099                        .collect::<Vec<_>>(),
17100                    vec!["method id()", "other"]
17101                )
17102            }
17103            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17104        }
17105    });
17106
17107    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
17108        let item1 = item1.clone();
17109        move |_, item_to_resolve, _| {
17110            let item1 = item1.clone();
17111            async move {
17112                if item1 == item_to_resolve {
17113                    Ok(lsp::CompletionItem {
17114                        label: "method id()".to_string(),
17115                        filter_text: Some("id".to_string()),
17116                        detail: Some("Now resolved!".to_string()),
17117                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
17118                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17119                            range: lsp::Range::new(
17120                                lsp::Position::new(0, 22),
17121                                lsp::Position::new(0, 22),
17122                            ),
17123                            new_text: ".id".to_string(),
17124                        })),
17125                        ..lsp::CompletionItem::default()
17126                    })
17127                } else {
17128                    Ok(item_to_resolve)
17129                }
17130            }
17131        }
17132    })
17133    .next()
17134    .await
17135    .unwrap();
17136    cx.run_until_parked();
17137
17138    cx.update_editor(|editor, window, cx| {
17139        editor.context_menu_next(&Default::default(), window, cx);
17140    });
17141
17142    cx.update_editor(|editor, _, _| {
17143        let context_menu = editor.context_menu.borrow_mut();
17144        let context_menu = context_menu
17145            .as_ref()
17146            .expect("Should have the context menu deployed");
17147        match context_menu {
17148            CodeContextMenu::Completions(completions_menu) => {
17149                let completions = completions_menu.completions.borrow_mut();
17150                assert_eq!(
17151                    completions
17152                        .iter()
17153                        .map(|completion| &completion.label.text)
17154                        .collect::<Vec<_>>(),
17155                    vec!["method id() Now resolved!", "other"],
17156                    "Should update first completion label, but not second as the filter text did not match."
17157                );
17158            }
17159            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17160        }
17161    });
17162}
17163
17164#[gpui::test]
17165async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
17166    init_test(cx, |_| {});
17167    let mut cx = EditorLspTestContext::new_rust(
17168        lsp::ServerCapabilities {
17169            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
17170            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
17171            completion_provider: Some(lsp::CompletionOptions {
17172                resolve_provider: Some(true),
17173                ..Default::default()
17174            }),
17175            ..Default::default()
17176        },
17177        cx,
17178    )
17179    .await;
17180    cx.set_state(indoc! {"
17181        struct TestStruct {
17182            field: i32
17183        }
17184
17185        fn mainˇ() {
17186            let unused_var = 42;
17187            let test_struct = TestStruct { field: 42 };
17188        }
17189    "});
17190    let symbol_range = cx.lsp_range(indoc! {"
17191        struct TestStruct {
17192            field: i32
17193        }
17194
17195        «fn main»() {
17196            let unused_var = 42;
17197            let test_struct = TestStruct { field: 42 };
17198        }
17199    "});
17200    let mut hover_requests =
17201        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
17202            Ok(Some(lsp::Hover {
17203                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
17204                    kind: lsp::MarkupKind::Markdown,
17205                    value: "Function documentation".to_string(),
17206                }),
17207                range: Some(symbol_range),
17208            }))
17209        });
17210
17211    // Case 1: Test that code action menu hide hover popover
17212    cx.dispatch_action(Hover);
17213    hover_requests.next().await;
17214    cx.condition(|editor, _| editor.hover_state.visible()).await;
17215    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
17216        move |_, _, _| async move {
17217            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
17218                lsp::CodeAction {
17219                    title: "Remove unused variable".to_string(),
17220                    kind: Some(CodeActionKind::QUICKFIX),
17221                    edit: Some(lsp::WorkspaceEdit {
17222                        changes: Some(
17223                            [(
17224                                lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
17225                                vec![lsp::TextEdit {
17226                                    range: lsp::Range::new(
17227                                        lsp::Position::new(5, 4),
17228                                        lsp::Position::new(5, 27),
17229                                    ),
17230                                    new_text: "".to_string(),
17231                                }],
17232                            )]
17233                            .into_iter()
17234                            .collect(),
17235                        ),
17236                        ..Default::default()
17237                    }),
17238                    ..Default::default()
17239                },
17240            )]))
17241        },
17242    );
17243    cx.update_editor(|editor, window, cx| {
17244        editor.toggle_code_actions(
17245            &ToggleCodeActions {
17246                deployed_from: None,
17247                quick_launch: false,
17248            },
17249            window,
17250            cx,
17251        );
17252    });
17253    code_action_requests.next().await;
17254    cx.run_until_parked();
17255    cx.condition(|editor, _| editor.context_menu_visible())
17256        .await;
17257    cx.update_editor(|editor, _, _| {
17258        assert!(
17259            !editor.hover_state.visible(),
17260            "Hover popover should be hidden when code action menu is shown"
17261        );
17262        // Hide code actions
17263        editor.context_menu.take();
17264    });
17265
17266    // Case 2: Test that code completions hide hover popover
17267    cx.dispatch_action(Hover);
17268    hover_requests.next().await;
17269    cx.condition(|editor, _| editor.hover_state.visible()).await;
17270    let counter = Arc::new(AtomicUsize::new(0));
17271    let mut completion_requests =
17272        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17273            let counter = counter.clone();
17274            async move {
17275                counter.fetch_add(1, atomic::Ordering::Release);
17276                Ok(Some(lsp::CompletionResponse::Array(vec![
17277                    lsp::CompletionItem {
17278                        label: "main".into(),
17279                        kind: Some(lsp::CompletionItemKind::FUNCTION),
17280                        detail: Some("() -> ()".to_string()),
17281                        ..Default::default()
17282                    },
17283                    lsp::CompletionItem {
17284                        label: "TestStruct".into(),
17285                        kind: Some(lsp::CompletionItemKind::STRUCT),
17286                        detail: Some("struct TestStruct".to_string()),
17287                        ..Default::default()
17288                    },
17289                ])))
17290            }
17291        });
17292    cx.update_editor(|editor, window, cx| {
17293        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
17294    });
17295    completion_requests.next().await;
17296    cx.condition(|editor, _| editor.context_menu_visible())
17297        .await;
17298    cx.update_editor(|editor, _, _| {
17299        assert!(
17300            !editor.hover_state.visible(),
17301            "Hover popover should be hidden when completion menu is shown"
17302        );
17303    });
17304}
17305
17306#[gpui::test]
17307async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
17308    init_test(cx, |_| {});
17309
17310    let mut cx = EditorLspTestContext::new_rust(
17311        lsp::ServerCapabilities {
17312            completion_provider: Some(lsp::CompletionOptions {
17313                trigger_characters: Some(vec![".".to_string()]),
17314                resolve_provider: Some(true),
17315                ..Default::default()
17316            }),
17317            ..Default::default()
17318        },
17319        cx,
17320    )
17321    .await;
17322
17323    cx.set_state("fn main() { let a = 2ˇ; }");
17324    cx.simulate_keystroke(".");
17325
17326    let unresolved_item_1 = lsp::CompletionItem {
17327        label: "id".to_string(),
17328        filter_text: Some("id".to_string()),
17329        detail: None,
17330        documentation: None,
17331        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17332            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17333            new_text: ".id".to_string(),
17334        })),
17335        ..lsp::CompletionItem::default()
17336    };
17337    let resolved_item_1 = lsp::CompletionItem {
17338        additional_text_edits: Some(vec![lsp::TextEdit {
17339            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
17340            new_text: "!!".to_string(),
17341        }]),
17342        ..unresolved_item_1.clone()
17343    };
17344    let unresolved_item_2 = lsp::CompletionItem {
17345        label: "other".to_string(),
17346        filter_text: Some("other".to_string()),
17347        detail: None,
17348        documentation: None,
17349        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17350            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17351            new_text: ".other".to_string(),
17352        })),
17353        ..lsp::CompletionItem::default()
17354    };
17355    let resolved_item_2 = lsp::CompletionItem {
17356        additional_text_edits: Some(vec![lsp::TextEdit {
17357            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
17358            new_text: "??".to_string(),
17359        }]),
17360        ..unresolved_item_2.clone()
17361    };
17362
17363    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
17364    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
17365    cx.lsp
17366        .server
17367        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
17368            let unresolved_item_1 = unresolved_item_1.clone();
17369            let resolved_item_1 = resolved_item_1.clone();
17370            let unresolved_item_2 = unresolved_item_2.clone();
17371            let resolved_item_2 = resolved_item_2.clone();
17372            let resolve_requests_1 = resolve_requests_1.clone();
17373            let resolve_requests_2 = resolve_requests_2.clone();
17374            move |unresolved_request, _| {
17375                let unresolved_item_1 = unresolved_item_1.clone();
17376                let resolved_item_1 = resolved_item_1.clone();
17377                let unresolved_item_2 = unresolved_item_2.clone();
17378                let resolved_item_2 = resolved_item_2.clone();
17379                let resolve_requests_1 = resolve_requests_1.clone();
17380                let resolve_requests_2 = resolve_requests_2.clone();
17381                async move {
17382                    if unresolved_request == unresolved_item_1 {
17383                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
17384                        Ok(resolved_item_1.clone())
17385                    } else if unresolved_request == unresolved_item_2 {
17386                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
17387                        Ok(resolved_item_2.clone())
17388                    } else {
17389                        panic!("Unexpected completion item {unresolved_request:?}")
17390                    }
17391                }
17392            }
17393        })
17394        .detach();
17395
17396    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17397        let unresolved_item_1 = unresolved_item_1.clone();
17398        let unresolved_item_2 = unresolved_item_2.clone();
17399        async move {
17400            Ok(Some(lsp::CompletionResponse::Array(vec![
17401                unresolved_item_1,
17402                unresolved_item_2,
17403            ])))
17404        }
17405    })
17406    .next()
17407    .await;
17408
17409    cx.condition(|editor, _| editor.context_menu_visible())
17410        .await;
17411    cx.update_editor(|editor, _, _| {
17412        let context_menu = editor.context_menu.borrow_mut();
17413        let context_menu = context_menu
17414            .as_ref()
17415            .expect("Should have the context menu deployed");
17416        match context_menu {
17417            CodeContextMenu::Completions(completions_menu) => {
17418                let completions = completions_menu.completions.borrow_mut();
17419                assert_eq!(
17420                    completions
17421                        .iter()
17422                        .map(|completion| &completion.label.text)
17423                        .collect::<Vec<_>>(),
17424                    vec!["id", "other"]
17425                )
17426            }
17427            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17428        }
17429    });
17430    cx.run_until_parked();
17431
17432    cx.update_editor(|editor, window, cx| {
17433        editor.context_menu_next(&ContextMenuNext, window, cx);
17434    });
17435    cx.run_until_parked();
17436    cx.update_editor(|editor, window, cx| {
17437        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
17438    });
17439    cx.run_until_parked();
17440    cx.update_editor(|editor, window, cx| {
17441        editor.context_menu_next(&ContextMenuNext, window, cx);
17442    });
17443    cx.run_until_parked();
17444    cx.update_editor(|editor, window, cx| {
17445        editor
17446            .compose_completion(&ComposeCompletion::default(), window, cx)
17447            .expect("No task returned")
17448    })
17449    .await
17450    .expect("Completion failed");
17451    cx.run_until_parked();
17452
17453    cx.update_editor(|editor, _, cx| {
17454        assert_eq!(
17455            resolve_requests_1.load(atomic::Ordering::Acquire),
17456            1,
17457            "Should always resolve once despite multiple selections"
17458        );
17459        assert_eq!(
17460            resolve_requests_2.load(atomic::Ordering::Acquire),
17461            1,
17462            "Should always resolve once after multiple selections and applying the completion"
17463        );
17464        assert_eq!(
17465            editor.text(cx),
17466            "fn main() { let a = ??.other; }",
17467            "Should use resolved data when applying the completion"
17468        );
17469    });
17470}
17471
17472#[gpui::test]
17473async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
17474    init_test(cx, |_| {});
17475
17476    let item_0 = lsp::CompletionItem {
17477        label: "abs".into(),
17478        insert_text: Some("abs".into()),
17479        data: Some(json!({ "very": "special"})),
17480        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
17481        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
17482            lsp::InsertReplaceEdit {
17483                new_text: "abs".to_string(),
17484                insert: lsp::Range::default(),
17485                replace: lsp::Range::default(),
17486            },
17487        )),
17488        ..lsp::CompletionItem::default()
17489    };
17490    let items = iter::once(item_0.clone())
17491        .chain((11..51).map(|i| lsp::CompletionItem {
17492            label: format!("item_{}", i),
17493            insert_text: Some(format!("item_{}", i)),
17494            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
17495            ..lsp::CompletionItem::default()
17496        }))
17497        .collect::<Vec<_>>();
17498
17499    let default_commit_characters = vec!["?".to_string()];
17500    let default_data = json!({ "default": "data"});
17501    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
17502    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
17503    let default_edit_range = lsp::Range {
17504        start: lsp::Position {
17505            line: 0,
17506            character: 5,
17507        },
17508        end: lsp::Position {
17509            line: 0,
17510            character: 5,
17511        },
17512    };
17513
17514    let mut cx = EditorLspTestContext::new_rust(
17515        lsp::ServerCapabilities {
17516            completion_provider: Some(lsp::CompletionOptions {
17517                trigger_characters: Some(vec![".".to_string()]),
17518                resolve_provider: Some(true),
17519                ..Default::default()
17520            }),
17521            ..Default::default()
17522        },
17523        cx,
17524    )
17525    .await;
17526
17527    cx.set_state("fn main() { let a = 2ˇ; }");
17528    cx.simulate_keystroke(".");
17529
17530    let completion_data = default_data.clone();
17531    let completion_characters = default_commit_characters.clone();
17532    let completion_items = items.clone();
17533    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17534        let default_data = completion_data.clone();
17535        let default_commit_characters = completion_characters.clone();
17536        let items = completion_items.clone();
17537        async move {
17538            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
17539                items,
17540                item_defaults: Some(lsp::CompletionListItemDefaults {
17541                    data: Some(default_data.clone()),
17542                    commit_characters: Some(default_commit_characters.clone()),
17543                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
17544                        default_edit_range,
17545                    )),
17546                    insert_text_format: Some(default_insert_text_format),
17547                    insert_text_mode: Some(default_insert_text_mode),
17548                }),
17549                ..lsp::CompletionList::default()
17550            })))
17551        }
17552    })
17553    .next()
17554    .await;
17555
17556    let resolved_items = Arc::new(Mutex::new(Vec::new()));
17557    cx.lsp
17558        .server
17559        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
17560            let closure_resolved_items = resolved_items.clone();
17561            move |item_to_resolve, _| {
17562                let closure_resolved_items = closure_resolved_items.clone();
17563                async move {
17564                    closure_resolved_items.lock().push(item_to_resolve.clone());
17565                    Ok(item_to_resolve)
17566                }
17567            }
17568        })
17569        .detach();
17570
17571    cx.condition(|editor, _| editor.context_menu_visible())
17572        .await;
17573    cx.run_until_parked();
17574    cx.update_editor(|editor, _, _| {
17575        let menu = editor.context_menu.borrow_mut();
17576        match menu.as_ref().expect("should have the completions menu") {
17577            CodeContextMenu::Completions(completions_menu) => {
17578                assert_eq!(
17579                    completions_menu
17580                        .entries
17581                        .borrow()
17582                        .iter()
17583                        .map(|mat| mat.string.clone())
17584                        .collect::<Vec<String>>(),
17585                    items
17586                        .iter()
17587                        .map(|completion| completion.label.clone())
17588                        .collect::<Vec<String>>()
17589                );
17590            }
17591            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
17592        }
17593    });
17594    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
17595    // with 4 from the end.
17596    assert_eq!(
17597        *resolved_items.lock(),
17598        [&items[0..16], &items[items.len() - 4..items.len()]]
17599            .concat()
17600            .iter()
17601            .cloned()
17602            .map(|mut item| {
17603                if item.data.is_none() {
17604                    item.data = Some(default_data.clone());
17605                }
17606                item
17607            })
17608            .collect::<Vec<lsp::CompletionItem>>(),
17609        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
17610    );
17611    resolved_items.lock().clear();
17612
17613    cx.update_editor(|editor, window, cx| {
17614        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
17615    });
17616    cx.run_until_parked();
17617    // Completions that have already been resolved are skipped.
17618    assert_eq!(
17619        *resolved_items.lock(),
17620        items[items.len() - 17..items.len() - 4]
17621            .iter()
17622            .cloned()
17623            .map(|mut item| {
17624                if item.data.is_none() {
17625                    item.data = Some(default_data.clone());
17626                }
17627                item
17628            })
17629            .collect::<Vec<lsp::CompletionItem>>()
17630    );
17631    resolved_items.lock().clear();
17632}
17633
17634#[gpui::test]
17635async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
17636    init_test(cx, |_| {});
17637
17638    let mut cx = EditorLspTestContext::new(
17639        Language::new(
17640            LanguageConfig {
17641                matcher: LanguageMatcher {
17642                    path_suffixes: vec!["jsx".into()],
17643                    ..Default::default()
17644                },
17645                overrides: [(
17646                    "element".into(),
17647                    LanguageConfigOverride {
17648                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
17649                        ..Default::default()
17650                    },
17651                )]
17652                .into_iter()
17653                .collect(),
17654                ..Default::default()
17655            },
17656            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
17657        )
17658        .with_override_query("(jsx_self_closing_element) @element")
17659        .unwrap(),
17660        lsp::ServerCapabilities {
17661            completion_provider: Some(lsp::CompletionOptions {
17662                trigger_characters: Some(vec![":".to_string()]),
17663                ..Default::default()
17664            }),
17665            ..Default::default()
17666        },
17667        cx,
17668    )
17669    .await;
17670
17671    cx.lsp
17672        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
17673            Ok(Some(lsp::CompletionResponse::Array(vec![
17674                lsp::CompletionItem {
17675                    label: "bg-blue".into(),
17676                    ..Default::default()
17677                },
17678                lsp::CompletionItem {
17679                    label: "bg-red".into(),
17680                    ..Default::default()
17681                },
17682                lsp::CompletionItem {
17683                    label: "bg-yellow".into(),
17684                    ..Default::default()
17685                },
17686            ])))
17687        });
17688
17689    cx.set_state(r#"<p class="bgˇ" />"#);
17690
17691    // Trigger completion when typing a dash, because the dash is an extra
17692    // word character in the 'element' scope, which contains the cursor.
17693    cx.simulate_keystroke("-");
17694    cx.executor().run_until_parked();
17695    cx.update_editor(|editor, _, _| {
17696        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17697        {
17698            assert_eq!(
17699                completion_menu_entries(menu),
17700                &["bg-blue", "bg-red", "bg-yellow"]
17701            );
17702        } else {
17703            panic!("expected completion menu to be open");
17704        }
17705    });
17706
17707    cx.simulate_keystroke("l");
17708    cx.executor().run_until_parked();
17709    cx.update_editor(|editor, _, _| {
17710        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17711        {
17712            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
17713        } else {
17714            panic!("expected completion menu to be open");
17715        }
17716    });
17717
17718    // When filtering completions, consider the character after the '-' to
17719    // be the start of a subword.
17720    cx.set_state(r#"<p class="yelˇ" />"#);
17721    cx.simulate_keystroke("l");
17722    cx.executor().run_until_parked();
17723    cx.update_editor(|editor, _, _| {
17724        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17725        {
17726            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
17727        } else {
17728            panic!("expected completion menu to be open");
17729        }
17730    });
17731}
17732
17733fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
17734    let entries = menu.entries.borrow();
17735    entries.iter().map(|mat| mat.string.clone()).collect()
17736}
17737
17738#[gpui::test]
17739async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
17740    init_test(cx, |settings| {
17741        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
17742            Formatter::Prettier,
17743        )))
17744    });
17745
17746    let fs = FakeFs::new(cx.executor());
17747    fs.insert_file(path!("/file.ts"), Default::default()).await;
17748
17749    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
17750    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17751
17752    language_registry.add(Arc::new(Language::new(
17753        LanguageConfig {
17754            name: "TypeScript".into(),
17755            matcher: LanguageMatcher {
17756                path_suffixes: vec!["ts".to_string()],
17757                ..Default::default()
17758            },
17759            ..Default::default()
17760        },
17761        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
17762    )));
17763    update_test_language_settings(cx, |settings| {
17764        settings.defaults.prettier = Some(PrettierSettings {
17765            allowed: true,
17766            ..PrettierSettings::default()
17767        });
17768    });
17769
17770    let test_plugin = "test_plugin";
17771    let _ = language_registry.register_fake_lsp(
17772        "TypeScript",
17773        FakeLspAdapter {
17774            prettier_plugins: vec![test_plugin],
17775            ..Default::default()
17776        },
17777    );
17778
17779    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
17780    let buffer = project
17781        .update(cx, |project, cx| {
17782            project.open_local_buffer(path!("/file.ts"), cx)
17783        })
17784        .await
17785        .unwrap();
17786
17787    let buffer_text = "one\ntwo\nthree\n";
17788    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
17789    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
17790    editor.update_in(cx, |editor, window, cx| {
17791        editor.set_text(buffer_text, window, cx)
17792    });
17793
17794    editor
17795        .update_in(cx, |editor, window, cx| {
17796            editor.perform_format(
17797                project.clone(),
17798                FormatTrigger::Manual,
17799                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
17800                window,
17801                cx,
17802            )
17803        })
17804        .unwrap()
17805        .await;
17806    assert_eq!(
17807        editor.update(cx, |editor, cx| editor.text(cx)),
17808        buffer_text.to_string() + prettier_format_suffix,
17809        "Test prettier formatting was not applied to the original buffer text",
17810    );
17811
17812    update_test_language_settings(cx, |settings| {
17813        settings.defaults.formatter = Some(SelectedFormatter::Auto)
17814    });
17815    let format = editor.update_in(cx, |editor, window, cx| {
17816        editor.perform_format(
17817            project.clone(),
17818            FormatTrigger::Manual,
17819            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
17820            window,
17821            cx,
17822        )
17823    });
17824    format.await.unwrap();
17825    assert_eq!(
17826        editor.update(cx, |editor, cx| editor.text(cx)),
17827        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
17828        "Autoformatting (via test prettier) was not applied to the original buffer text",
17829    );
17830}
17831
17832#[gpui::test]
17833async fn test_addition_reverts(cx: &mut TestAppContext) {
17834    init_test(cx, |_| {});
17835    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
17836    let base_text = indoc! {r#"
17837        struct Row;
17838        struct Row1;
17839        struct Row2;
17840
17841        struct Row4;
17842        struct Row5;
17843        struct Row6;
17844
17845        struct Row8;
17846        struct Row9;
17847        struct Row10;"#};
17848
17849    // When addition hunks are not adjacent to carets, no hunk revert is performed
17850    assert_hunk_revert(
17851        indoc! {r#"struct Row;
17852                   struct Row1;
17853                   struct Row1.1;
17854                   struct Row1.2;
17855                   struct Row2;ˇ
17856
17857                   struct Row4;
17858                   struct Row5;
17859                   struct Row6;
17860
17861                   struct Row8;
17862                   ˇstruct Row9;
17863                   struct Row9.1;
17864                   struct Row9.2;
17865                   struct Row9.3;
17866                   struct Row10;"#},
17867        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
17868        indoc! {r#"struct Row;
17869                   struct Row1;
17870                   struct Row1.1;
17871                   struct Row1.2;
17872                   struct Row2;ˇ
17873
17874                   struct Row4;
17875                   struct Row5;
17876                   struct Row6;
17877
17878                   struct Row8;
17879                   ˇstruct Row9;
17880                   struct Row9.1;
17881                   struct Row9.2;
17882                   struct Row9.3;
17883                   struct Row10;"#},
17884        base_text,
17885        &mut cx,
17886    );
17887    // Same for selections
17888    assert_hunk_revert(
17889        indoc! {r#"struct Row;
17890                   struct Row1;
17891                   struct Row2;
17892                   struct Row2.1;
17893                   struct Row2.2;
17894                   «ˇ
17895                   struct Row4;
17896                   struct» Row5;
17897                   «struct Row6;
17898                   ˇ»
17899                   struct Row9.1;
17900                   struct Row9.2;
17901                   struct Row9.3;
17902                   struct Row8;
17903                   struct Row9;
17904                   struct Row10;"#},
17905        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
17906        indoc! {r#"struct Row;
17907                   struct Row1;
17908                   struct Row2;
17909                   struct Row2.1;
17910                   struct Row2.2;
17911                   «ˇ
17912                   struct Row4;
17913                   struct» Row5;
17914                   «struct Row6;
17915                   ˇ»
17916                   struct Row9.1;
17917                   struct Row9.2;
17918                   struct Row9.3;
17919                   struct Row8;
17920                   struct Row9;
17921                   struct Row10;"#},
17922        base_text,
17923        &mut cx,
17924    );
17925
17926    // When carets and selections intersect the addition hunks, those are reverted.
17927    // Adjacent carets got merged.
17928    assert_hunk_revert(
17929        indoc! {r#"struct Row;
17930                   ˇ// something on the top
17931                   struct Row1;
17932                   struct Row2;
17933                   struct Roˇw3.1;
17934                   struct Row2.2;
17935                   struct Row2.3;ˇ
17936
17937                   struct Row4;
17938                   struct ˇRow5.1;
17939                   struct Row5.2;
17940                   struct «Rowˇ»5.3;
17941                   struct Row5;
17942                   struct Row6;
17943                   ˇ
17944                   struct Row9.1;
17945                   struct «Rowˇ»9.2;
17946                   struct «ˇRow»9.3;
17947                   struct Row8;
17948                   struct Row9;
17949                   «ˇ// something on bottom»
17950                   struct Row10;"#},
17951        vec![
17952            DiffHunkStatusKind::Added,
17953            DiffHunkStatusKind::Added,
17954            DiffHunkStatusKind::Added,
17955            DiffHunkStatusKind::Added,
17956            DiffHunkStatusKind::Added,
17957        ],
17958        indoc! {r#"struct Row;
17959                   ˇstruct Row1;
17960                   struct Row2;
17961                   ˇ
17962                   struct Row4;
17963                   ˇstruct Row5;
17964                   struct Row6;
17965                   ˇ
17966                   ˇstruct Row8;
17967                   struct Row9;
17968                   ˇstruct Row10;"#},
17969        base_text,
17970        &mut cx,
17971    );
17972}
17973
17974#[gpui::test]
17975async fn test_modification_reverts(cx: &mut TestAppContext) {
17976    init_test(cx, |_| {});
17977    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
17978    let base_text = indoc! {r#"
17979        struct Row;
17980        struct Row1;
17981        struct Row2;
17982
17983        struct Row4;
17984        struct Row5;
17985        struct Row6;
17986
17987        struct Row8;
17988        struct Row9;
17989        struct Row10;"#};
17990
17991    // Modification hunks behave the same as the addition ones.
17992    assert_hunk_revert(
17993        indoc! {r#"struct Row;
17994                   struct Row1;
17995                   struct Row33;
17996                   ˇ
17997                   struct Row4;
17998                   struct Row5;
17999                   struct Row6;
18000                   ˇ
18001                   struct Row99;
18002                   struct Row9;
18003                   struct Row10;"#},
18004        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18005        indoc! {r#"struct Row;
18006                   struct Row1;
18007                   struct Row33;
18008                   ˇ
18009                   struct Row4;
18010                   struct Row5;
18011                   struct Row6;
18012                   ˇ
18013                   struct Row99;
18014                   struct Row9;
18015                   struct Row10;"#},
18016        base_text,
18017        &mut cx,
18018    );
18019    assert_hunk_revert(
18020        indoc! {r#"struct Row;
18021                   struct Row1;
18022                   struct Row33;
18023                   «ˇ
18024                   struct Row4;
18025                   struct» Row5;
18026                   «struct Row6;
18027                   ˇ»
18028                   struct Row99;
18029                   struct Row9;
18030                   struct Row10;"#},
18031        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18032        indoc! {r#"struct Row;
18033                   struct Row1;
18034                   struct Row33;
18035                   «ˇ
18036                   struct Row4;
18037                   struct» Row5;
18038                   «struct Row6;
18039                   ˇ»
18040                   struct Row99;
18041                   struct Row9;
18042                   struct Row10;"#},
18043        base_text,
18044        &mut cx,
18045    );
18046
18047    assert_hunk_revert(
18048        indoc! {r#"ˇstruct Row1.1;
18049                   struct Row1;
18050                   «ˇstr»uct Row22;
18051
18052                   struct ˇRow44;
18053                   struct Row5;
18054                   struct «Rˇ»ow66;ˇ
18055
18056                   «struˇ»ct Row88;
18057                   struct Row9;
18058                   struct Row1011;ˇ"#},
18059        vec![
18060            DiffHunkStatusKind::Modified,
18061            DiffHunkStatusKind::Modified,
18062            DiffHunkStatusKind::Modified,
18063            DiffHunkStatusKind::Modified,
18064            DiffHunkStatusKind::Modified,
18065            DiffHunkStatusKind::Modified,
18066        ],
18067        indoc! {r#"struct Row;
18068                   ˇstruct Row1;
18069                   struct Row2;
18070                   ˇ
18071                   struct Row4;
18072                   ˇstruct Row5;
18073                   struct Row6;
18074                   ˇ
18075                   struct Row8;
18076                   ˇstruct Row9;
18077                   struct Row10;ˇ"#},
18078        base_text,
18079        &mut cx,
18080    );
18081}
18082
18083#[gpui::test]
18084async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
18085    init_test(cx, |_| {});
18086    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18087    let base_text = indoc! {r#"
18088        one
18089
18090        two
18091        three
18092        "#};
18093
18094    cx.set_head_text(base_text);
18095    cx.set_state("\nˇ\n");
18096    cx.executor().run_until_parked();
18097    cx.update_editor(|editor, _window, cx| {
18098        editor.expand_selected_diff_hunks(cx);
18099    });
18100    cx.executor().run_until_parked();
18101    cx.update_editor(|editor, window, cx| {
18102        editor.backspace(&Default::default(), window, cx);
18103    });
18104    cx.run_until_parked();
18105    cx.assert_state_with_diff(
18106        indoc! {r#"
18107
18108        - two
18109        - threeˇ
18110        +
18111        "#}
18112        .to_string(),
18113    );
18114}
18115
18116#[gpui::test]
18117async fn test_deletion_reverts(cx: &mut TestAppContext) {
18118    init_test(cx, |_| {});
18119    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18120    let base_text = indoc! {r#"struct Row;
18121struct Row1;
18122struct Row2;
18123
18124struct Row4;
18125struct Row5;
18126struct Row6;
18127
18128struct Row8;
18129struct Row9;
18130struct Row10;"#};
18131
18132    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
18133    assert_hunk_revert(
18134        indoc! {r#"struct Row;
18135                   struct Row2;
18136
18137                   ˇstruct Row4;
18138                   struct Row5;
18139                   struct Row6;
18140                   ˇ
18141                   struct Row8;
18142                   struct Row10;"#},
18143        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18144        indoc! {r#"struct Row;
18145                   struct Row2;
18146
18147                   ˇstruct Row4;
18148                   struct Row5;
18149                   struct Row6;
18150                   ˇ
18151                   struct Row8;
18152                   struct Row10;"#},
18153        base_text,
18154        &mut cx,
18155    );
18156    assert_hunk_revert(
18157        indoc! {r#"struct Row;
18158                   struct Row2;
18159
18160                   «ˇstruct Row4;
18161                   struct» Row5;
18162                   «struct Row6;
18163                   ˇ»
18164                   struct Row8;
18165                   struct Row10;"#},
18166        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18167        indoc! {r#"struct Row;
18168                   struct Row2;
18169
18170                   «ˇstruct Row4;
18171                   struct» Row5;
18172                   «struct Row6;
18173                   ˇ»
18174                   struct Row8;
18175                   struct Row10;"#},
18176        base_text,
18177        &mut cx,
18178    );
18179
18180    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
18181    assert_hunk_revert(
18182        indoc! {r#"struct Row;
18183                   ˇstruct Row2;
18184
18185                   struct Row4;
18186                   struct Row5;
18187                   struct Row6;
18188
18189                   struct Row8;ˇ
18190                   struct Row10;"#},
18191        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18192        indoc! {r#"struct Row;
18193                   struct Row1;
18194                   ˇstruct Row2;
18195
18196                   struct Row4;
18197                   struct Row5;
18198                   struct Row6;
18199
18200                   struct Row8;ˇ
18201                   struct Row9;
18202                   struct Row10;"#},
18203        base_text,
18204        &mut cx,
18205    );
18206    assert_hunk_revert(
18207        indoc! {r#"struct Row;
18208                   struct Row2«ˇ;
18209                   struct Row4;
18210                   struct» Row5;
18211                   «struct Row6;
18212
18213                   struct Row8;ˇ»
18214                   struct Row10;"#},
18215        vec![
18216            DiffHunkStatusKind::Deleted,
18217            DiffHunkStatusKind::Deleted,
18218            DiffHunkStatusKind::Deleted,
18219        ],
18220        indoc! {r#"struct Row;
18221                   struct Row1;
18222                   struct Row2«ˇ;
18223
18224                   struct Row4;
18225                   struct» Row5;
18226                   «struct Row6;
18227
18228                   struct Row8;ˇ»
18229                   struct Row9;
18230                   struct Row10;"#},
18231        base_text,
18232        &mut cx,
18233    );
18234}
18235
18236#[gpui::test]
18237async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
18238    init_test(cx, |_| {});
18239
18240    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
18241    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
18242    let base_text_3 =
18243        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
18244
18245    let text_1 = edit_first_char_of_every_line(base_text_1);
18246    let text_2 = edit_first_char_of_every_line(base_text_2);
18247    let text_3 = edit_first_char_of_every_line(base_text_3);
18248
18249    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
18250    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
18251    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
18252
18253    let multibuffer = cx.new(|cx| {
18254        let mut multibuffer = MultiBuffer::new(ReadWrite);
18255        multibuffer.push_excerpts(
18256            buffer_1.clone(),
18257            [
18258                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18259                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18260                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18261            ],
18262            cx,
18263        );
18264        multibuffer.push_excerpts(
18265            buffer_2.clone(),
18266            [
18267                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18268                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18269                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18270            ],
18271            cx,
18272        );
18273        multibuffer.push_excerpts(
18274            buffer_3.clone(),
18275            [
18276                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18277                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18278                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18279            ],
18280            cx,
18281        );
18282        multibuffer
18283    });
18284
18285    let fs = FakeFs::new(cx.executor());
18286    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
18287    let (editor, cx) = cx
18288        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
18289    editor.update_in(cx, |editor, _window, cx| {
18290        for (buffer, diff_base) in [
18291            (buffer_1.clone(), base_text_1),
18292            (buffer_2.clone(), base_text_2),
18293            (buffer_3.clone(), base_text_3),
18294        ] {
18295            let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
18296            editor
18297                .buffer
18298                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
18299        }
18300    });
18301    cx.executor().run_until_parked();
18302
18303    editor.update_in(cx, |editor, window, cx| {
18304        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}");
18305        editor.select_all(&SelectAll, window, cx);
18306        editor.git_restore(&Default::default(), window, cx);
18307    });
18308    cx.executor().run_until_parked();
18309
18310    // When all ranges are selected, all buffer hunks are reverted.
18311    editor.update(cx, |editor, cx| {
18312        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");
18313    });
18314    buffer_1.update(cx, |buffer, _| {
18315        assert_eq!(buffer.text(), base_text_1);
18316    });
18317    buffer_2.update(cx, |buffer, _| {
18318        assert_eq!(buffer.text(), base_text_2);
18319    });
18320    buffer_3.update(cx, |buffer, _| {
18321        assert_eq!(buffer.text(), base_text_3);
18322    });
18323
18324    editor.update_in(cx, |editor, window, cx| {
18325        editor.undo(&Default::default(), window, cx);
18326    });
18327
18328    editor.update_in(cx, |editor, window, cx| {
18329        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18330            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
18331        });
18332        editor.git_restore(&Default::default(), window, cx);
18333    });
18334
18335    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
18336    // but not affect buffer_2 and its related excerpts.
18337    editor.update(cx, |editor, cx| {
18338        assert_eq!(
18339            editor.text(cx),
18340            "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}"
18341        );
18342    });
18343    buffer_1.update(cx, |buffer, _| {
18344        assert_eq!(buffer.text(), base_text_1);
18345    });
18346    buffer_2.update(cx, |buffer, _| {
18347        assert_eq!(
18348            buffer.text(),
18349            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
18350        );
18351    });
18352    buffer_3.update(cx, |buffer, _| {
18353        assert_eq!(
18354            buffer.text(),
18355            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
18356        );
18357    });
18358
18359    fn edit_first_char_of_every_line(text: &str) -> String {
18360        text.split('\n')
18361            .map(|line| format!("X{}", &line[1..]))
18362            .collect::<Vec<_>>()
18363            .join("\n")
18364    }
18365}
18366
18367#[gpui::test]
18368async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
18369    init_test(cx, |_| {});
18370
18371    let cols = 4;
18372    let rows = 10;
18373    let sample_text_1 = sample_text(rows, cols, 'a');
18374    assert_eq!(
18375        sample_text_1,
18376        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
18377    );
18378    let sample_text_2 = sample_text(rows, cols, 'l');
18379    assert_eq!(
18380        sample_text_2,
18381        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
18382    );
18383    let sample_text_3 = sample_text(rows, cols, 'v');
18384    assert_eq!(
18385        sample_text_3,
18386        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
18387    );
18388
18389    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
18390    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
18391    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
18392
18393    let multi_buffer = cx.new(|cx| {
18394        let mut multibuffer = MultiBuffer::new(ReadWrite);
18395        multibuffer.push_excerpts(
18396            buffer_1.clone(),
18397            [
18398                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18399                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18400                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18401            ],
18402            cx,
18403        );
18404        multibuffer.push_excerpts(
18405            buffer_2.clone(),
18406            [
18407                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18408                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18409                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18410            ],
18411            cx,
18412        );
18413        multibuffer.push_excerpts(
18414            buffer_3.clone(),
18415            [
18416                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18417                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18418                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18419            ],
18420            cx,
18421        );
18422        multibuffer
18423    });
18424
18425    let fs = FakeFs::new(cx.executor());
18426    fs.insert_tree(
18427        "/a",
18428        json!({
18429            "main.rs": sample_text_1,
18430            "other.rs": sample_text_2,
18431            "lib.rs": sample_text_3,
18432        }),
18433    )
18434    .await;
18435    let project = Project::test(fs, ["/a".as_ref()], cx).await;
18436    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18437    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18438    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
18439        Editor::new(
18440            EditorMode::full(),
18441            multi_buffer,
18442            Some(project.clone()),
18443            window,
18444            cx,
18445        )
18446    });
18447    let multibuffer_item_id = workspace
18448        .update(cx, |workspace, window, cx| {
18449            assert!(
18450                workspace.active_item(cx).is_none(),
18451                "active item should be None before the first item is added"
18452            );
18453            workspace.add_item_to_active_pane(
18454                Box::new(multi_buffer_editor.clone()),
18455                None,
18456                true,
18457                window,
18458                cx,
18459            );
18460            let active_item = workspace
18461                .active_item(cx)
18462                .expect("should have an active item after adding the multi buffer");
18463            assert!(
18464                !active_item.is_singleton(cx),
18465                "A multi buffer was expected to active after adding"
18466            );
18467            active_item.item_id()
18468        })
18469        .unwrap();
18470    cx.executor().run_until_parked();
18471
18472    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18473        editor.change_selections(
18474            SelectionEffects::scroll(Autoscroll::Next),
18475            window,
18476            cx,
18477            |s| s.select_ranges(Some(1..2)),
18478        );
18479        editor.open_excerpts(&OpenExcerpts, window, cx);
18480    });
18481    cx.executor().run_until_parked();
18482    let first_item_id = workspace
18483        .update(cx, |workspace, window, cx| {
18484            let active_item = workspace
18485                .active_item(cx)
18486                .expect("should have an active item after navigating into the 1st buffer");
18487            let first_item_id = active_item.item_id();
18488            assert_ne!(
18489                first_item_id, multibuffer_item_id,
18490                "Should navigate into the 1st buffer and activate it"
18491            );
18492            assert!(
18493                active_item.is_singleton(cx),
18494                "New active item should be a singleton buffer"
18495            );
18496            assert_eq!(
18497                active_item
18498                    .act_as::<Editor>(cx)
18499                    .expect("should have navigated into an editor for the 1st buffer")
18500                    .read(cx)
18501                    .text(cx),
18502                sample_text_1
18503            );
18504
18505            workspace
18506                .go_back(workspace.active_pane().downgrade(), window, cx)
18507                .detach_and_log_err(cx);
18508
18509            first_item_id
18510        })
18511        .unwrap();
18512    cx.executor().run_until_parked();
18513    workspace
18514        .update(cx, |workspace, _, cx| {
18515            let active_item = workspace
18516                .active_item(cx)
18517                .expect("should have an active item after navigating back");
18518            assert_eq!(
18519                active_item.item_id(),
18520                multibuffer_item_id,
18521                "Should navigate back to the multi buffer"
18522            );
18523            assert!(!active_item.is_singleton(cx));
18524        })
18525        .unwrap();
18526
18527    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18528        editor.change_selections(
18529            SelectionEffects::scroll(Autoscroll::Next),
18530            window,
18531            cx,
18532            |s| s.select_ranges(Some(39..40)),
18533        );
18534        editor.open_excerpts(&OpenExcerpts, window, cx);
18535    });
18536    cx.executor().run_until_parked();
18537    let second_item_id = workspace
18538        .update(cx, |workspace, window, cx| {
18539            let active_item = workspace
18540                .active_item(cx)
18541                .expect("should have an active item after navigating into the 2nd buffer");
18542            let second_item_id = active_item.item_id();
18543            assert_ne!(
18544                second_item_id, multibuffer_item_id,
18545                "Should navigate away from the multibuffer"
18546            );
18547            assert_ne!(
18548                second_item_id, first_item_id,
18549                "Should navigate into the 2nd buffer and activate it"
18550            );
18551            assert!(
18552                active_item.is_singleton(cx),
18553                "New active item should be a singleton buffer"
18554            );
18555            assert_eq!(
18556                active_item
18557                    .act_as::<Editor>(cx)
18558                    .expect("should have navigated into an editor")
18559                    .read(cx)
18560                    .text(cx),
18561                sample_text_2
18562            );
18563
18564            workspace
18565                .go_back(workspace.active_pane().downgrade(), window, cx)
18566                .detach_and_log_err(cx);
18567
18568            second_item_id
18569        })
18570        .unwrap();
18571    cx.executor().run_until_parked();
18572    workspace
18573        .update(cx, |workspace, _, cx| {
18574            let active_item = workspace
18575                .active_item(cx)
18576                .expect("should have an active item after navigating back from the 2nd buffer");
18577            assert_eq!(
18578                active_item.item_id(),
18579                multibuffer_item_id,
18580                "Should navigate back from the 2nd buffer to the multi buffer"
18581            );
18582            assert!(!active_item.is_singleton(cx));
18583        })
18584        .unwrap();
18585
18586    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18587        editor.change_selections(
18588            SelectionEffects::scroll(Autoscroll::Next),
18589            window,
18590            cx,
18591            |s| s.select_ranges(Some(70..70)),
18592        );
18593        editor.open_excerpts(&OpenExcerpts, window, cx);
18594    });
18595    cx.executor().run_until_parked();
18596    workspace
18597        .update(cx, |workspace, window, cx| {
18598            let active_item = workspace
18599                .active_item(cx)
18600                .expect("should have an active item after navigating into the 3rd buffer");
18601            let third_item_id = active_item.item_id();
18602            assert_ne!(
18603                third_item_id, multibuffer_item_id,
18604                "Should navigate into the 3rd buffer and activate it"
18605            );
18606            assert_ne!(third_item_id, first_item_id);
18607            assert_ne!(third_item_id, second_item_id);
18608            assert!(
18609                active_item.is_singleton(cx),
18610                "New active item should be a singleton buffer"
18611            );
18612            assert_eq!(
18613                active_item
18614                    .act_as::<Editor>(cx)
18615                    .expect("should have navigated into an editor")
18616                    .read(cx)
18617                    .text(cx),
18618                sample_text_3
18619            );
18620
18621            workspace
18622                .go_back(workspace.active_pane().downgrade(), window, cx)
18623                .detach_and_log_err(cx);
18624        })
18625        .unwrap();
18626    cx.executor().run_until_parked();
18627    workspace
18628        .update(cx, |workspace, _, cx| {
18629            let active_item = workspace
18630                .active_item(cx)
18631                .expect("should have an active item after navigating back from the 3rd buffer");
18632            assert_eq!(
18633                active_item.item_id(),
18634                multibuffer_item_id,
18635                "Should navigate back from the 3rd buffer to the multi buffer"
18636            );
18637            assert!(!active_item.is_singleton(cx));
18638        })
18639        .unwrap();
18640}
18641
18642#[gpui::test]
18643async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
18644    init_test(cx, |_| {});
18645
18646    let mut cx = EditorTestContext::new(cx).await;
18647
18648    let diff_base = r#"
18649        use some::mod;
18650
18651        const A: u32 = 42;
18652
18653        fn main() {
18654            println!("hello");
18655
18656            println!("world");
18657        }
18658        "#
18659    .unindent();
18660
18661    cx.set_state(
18662        &r#"
18663        use some::modified;
18664
18665        ˇ
18666        fn main() {
18667            println!("hello there");
18668
18669            println!("around the");
18670            println!("world");
18671        }
18672        "#
18673        .unindent(),
18674    );
18675
18676    cx.set_head_text(&diff_base);
18677    executor.run_until_parked();
18678
18679    cx.update_editor(|editor, window, cx| {
18680        editor.go_to_next_hunk(&GoToHunk, window, cx);
18681        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
18682    });
18683    executor.run_until_parked();
18684    cx.assert_state_with_diff(
18685        r#"
18686          use some::modified;
18687
18688
18689          fn main() {
18690        -     println!("hello");
18691        + ˇ    println!("hello there");
18692
18693              println!("around the");
18694              println!("world");
18695          }
18696        "#
18697        .unindent(),
18698    );
18699
18700    cx.update_editor(|editor, window, cx| {
18701        for _ in 0..2 {
18702            editor.go_to_next_hunk(&GoToHunk, window, cx);
18703            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
18704        }
18705    });
18706    executor.run_until_parked();
18707    cx.assert_state_with_diff(
18708        r#"
18709        - use some::mod;
18710        + ˇuse some::modified;
18711
18712
18713          fn main() {
18714        -     println!("hello");
18715        +     println!("hello there");
18716
18717        +     println!("around the");
18718              println!("world");
18719          }
18720        "#
18721        .unindent(),
18722    );
18723
18724    cx.update_editor(|editor, window, cx| {
18725        editor.go_to_next_hunk(&GoToHunk, window, cx);
18726        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
18727    });
18728    executor.run_until_parked();
18729    cx.assert_state_with_diff(
18730        r#"
18731        - use some::mod;
18732        + use some::modified;
18733
18734        - const A: u32 = 42;
18735          ˇ
18736          fn main() {
18737        -     println!("hello");
18738        +     println!("hello there");
18739
18740        +     println!("around the");
18741              println!("world");
18742          }
18743        "#
18744        .unindent(),
18745    );
18746
18747    cx.update_editor(|editor, window, cx| {
18748        editor.cancel(&Cancel, window, cx);
18749    });
18750
18751    cx.assert_state_with_diff(
18752        r#"
18753          use some::modified;
18754
18755          ˇ
18756          fn main() {
18757              println!("hello there");
18758
18759              println!("around the");
18760              println!("world");
18761          }
18762        "#
18763        .unindent(),
18764    );
18765}
18766
18767#[gpui::test]
18768async fn test_diff_base_change_with_expanded_diff_hunks(
18769    executor: BackgroundExecutor,
18770    cx: &mut TestAppContext,
18771) {
18772    init_test(cx, |_| {});
18773
18774    let mut cx = EditorTestContext::new(cx).await;
18775
18776    let diff_base = r#"
18777        use some::mod1;
18778        use some::mod2;
18779
18780        const A: u32 = 42;
18781        const B: u32 = 42;
18782        const C: u32 = 42;
18783
18784        fn main() {
18785            println!("hello");
18786
18787            println!("world");
18788        }
18789        "#
18790    .unindent();
18791
18792    cx.set_state(
18793        &r#"
18794        use some::mod2;
18795
18796        const A: u32 = 42;
18797        const C: u32 = 42;
18798
18799        fn main(ˇ) {
18800            //println!("hello");
18801
18802            println!("world");
18803            //
18804            //
18805        }
18806        "#
18807        .unindent(),
18808    );
18809
18810    cx.set_head_text(&diff_base);
18811    executor.run_until_parked();
18812
18813    cx.update_editor(|editor, window, cx| {
18814        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18815    });
18816    executor.run_until_parked();
18817    cx.assert_state_with_diff(
18818        r#"
18819        - use some::mod1;
18820          use some::mod2;
18821
18822          const A: u32 = 42;
18823        - const B: u32 = 42;
18824          const C: u32 = 42;
18825
18826          fn main(ˇ) {
18827        -     println!("hello");
18828        +     //println!("hello");
18829
18830              println!("world");
18831        +     //
18832        +     //
18833          }
18834        "#
18835        .unindent(),
18836    );
18837
18838    cx.set_head_text("new diff base!");
18839    executor.run_until_parked();
18840    cx.assert_state_with_diff(
18841        r#"
18842        - new diff base!
18843        + use some::mod2;
18844        +
18845        + const A: u32 = 42;
18846        + const C: u32 = 42;
18847        +
18848        + fn main(ˇ) {
18849        +     //println!("hello");
18850        +
18851        +     println!("world");
18852        +     //
18853        +     //
18854        + }
18855        "#
18856        .unindent(),
18857    );
18858}
18859
18860#[gpui::test]
18861async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
18862    init_test(cx, |_| {});
18863
18864    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
18865    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
18866    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
18867    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
18868    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
18869    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
18870
18871    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
18872    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
18873    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
18874
18875    let multi_buffer = cx.new(|cx| {
18876        let mut multibuffer = MultiBuffer::new(ReadWrite);
18877        multibuffer.push_excerpts(
18878            buffer_1.clone(),
18879            [
18880                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18881                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18882                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
18883            ],
18884            cx,
18885        );
18886        multibuffer.push_excerpts(
18887            buffer_2.clone(),
18888            [
18889                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18890                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18891                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
18892            ],
18893            cx,
18894        );
18895        multibuffer.push_excerpts(
18896            buffer_3.clone(),
18897            [
18898                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18899                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18900                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
18901            ],
18902            cx,
18903        );
18904        multibuffer
18905    });
18906
18907    let editor =
18908        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
18909    editor
18910        .update(cx, |editor, _window, cx| {
18911            for (buffer, diff_base) in [
18912                (buffer_1.clone(), file_1_old),
18913                (buffer_2.clone(), file_2_old),
18914                (buffer_3.clone(), file_3_old),
18915            ] {
18916                let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
18917                editor
18918                    .buffer
18919                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
18920            }
18921        })
18922        .unwrap();
18923
18924    let mut cx = EditorTestContext::for_editor(editor, cx).await;
18925    cx.run_until_parked();
18926
18927    cx.assert_editor_state(
18928        &"
18929            ˇaaa
18930            ccc
18931            ddd
18932
18933            ggg
18934            hhh
18935
18936
18937            lll
18938            mmm
18939            NNN
18940
18941            qqq
18942            rrr
18943
18944            uuu
18945            111
18946            222
18947            333
18948
18949            666
18950            777
18951
18952            000
18953            !!!"
18954        .unindent(),
18955    );
18956
18957    cx.update_editor(|editor, window, cx| {
18958        editor.select_all(&SelectAll, window, cx);
18959        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
18960    });
18961    cx.executor().run_until_parked();
18962
18963    cx.assert_state_with_diff(
18964        "
18965            «aaa
18966          - bbb
18967            ccc
18968            ddd
18969
18970            ggg
18971            hhh
18972
18973
18974            lll
18975            mmm
18976          - nnn
18977          + NNN
18978
18979            qqq
18980            rrr
18981
18982            uuu
18983            111
18984            222
18985            333
18986
18987          + 666
18988            777
18989
18990            000
18991            !!!ˇ»"
18992            .unindent(),
18993    );
18994}
18995
18996#[gpui::test]
18997async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
18998    init_test(cx, |_| {});
18999
19000    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
19001    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
19002
19003    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
19004    let multi_buffer = cx.new(|cx| {
19005        let mut multibuffer = MultiBuffer::new(ReadWrite);
19006        multibuffer.push_excerpts(
19007            buffer.clone(),
19008            [
19009                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
19010                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
19011                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
19012            ],
19013            cx,
19014        );
19015        multibuffer
19016    });
19017
19018    let editor =
19019        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19020    editor
19021        .update(cx, |editor, _window, cx| {
19022            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
19023            editor
19024                .buffer
19025                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
19026        })
19027        .unwrap();
19028
19029    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19030    cx.run_until_parked();
19031
19032    cx.update_editor(|editor, window, cx| {
19033        editor.expand_all_diff_hunks(&Default::default(), window, cx)
19034    });
19035    cx.executor().run_until_parked();
19036
19037    // When the start of a hunk coincides with the start of its excerpt,
19038    // the hunk is expanded. When the start of a a hunk is earlier than
19039    // the start of its excerpt, the hunk is not expanded.
19040    cx.assert_state_with_diff(
19041        "
19042            ˇaaa
19043          - bbb
19044          + BBB
19045
19046          - ddd
19047          - eee
19048          + DDD
19049          + EEE
19050            fff
19051
19052            iii
19053        "
19054        .unindent(),
19055    );
19056}
19057
19058#[gpui::test]
19059async fn test_edits_around_expanded_insertion_hunks(
19060    executor: BackgroundExecutor,
19061    cx: &mut TestAppContext,
19062) {
19063    init_test(cx, |_| {});
19064
19065    let mut cx = EditorTestContext::new(cx).await;
19066
19067    let diff_base = r#"
19068        use some::mod1;
19069        use some::mod2;
19070
19071        const A: u32 = 42;
19072
19073        fn main() {
19074            println!("hello");
19075
19076            println!("world");
19077        }
19078        "#
19079    .unindent();
19080    executor.run_until_parked();
19081    cx.set_state(
19082        &r#"
19083        use some::mod1;
19084        use some::mod2;
19085
19086        const A: u32 = 42;
19087        const B: u32 = 42;
19088        const C: u32 = 42;
19089        ˇ
19090
19091        fn main() {
19092            println!("hello");
19093
19094            println!("world");
19095        }
19096        "#
19097        .unindent(),
19098    );
19099
19100    cx.set_head_text(&diff_base);
19101    executor.run_until_parked();
19102
19103    cx.update_editor(|editor, window, cx| {
19104        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19105    });
19106    executor.run_until_parked();
19107
19108    cx.assert_state_with_diff(
19109        r#"
19110        use some::mod1;
19111        use some::mod2;
19112
19113        const A: u32 = 42;
19114      + const B: u32 = 42;
19115      + const C: u32 = 42;
19116      + ˇ
19117
19118        fn main() {
19119            println!("hello");
19120
19121            println!("world");
19122        }
19123      "#
19124        .unindent(),
19125    );
19126
19127    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
19128    executor.run_until_parked();
19129
19130    cx.assert_state_with_diff(
19131        r#"
19132        use some::mod1;
19133        use some::mod2;
19134
19135        const A: u32 = 42;
19136      + const B: u32 = 42;
19137      + const C: u32 = 42;
19138      + const D: u32 = 42;
19139      + ˇ
19140
19141        fn main() {
19142            println!("hello");
19143
19144            println!("world");
19145        }
19146      "#
19147        .unindent(),
19148    );
19149
19150    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
19151    executor.run_until_parked();
19152
19153    cx.assert_state_with_diff(
19154        r#"
19155        use some::mod1;
19156        use some::mod2;
19157
19158        const A: u32 = 42;
19159      + const B: u32 = 42;
19160      + const C: u32 = 42;
19161      + const D: u32 = 42;
19162      + const E: u32 = 42;
19163      + ˇ
19164
19165        fn main() {
19166            println!("hello");
19167
19168            println!("world");
19169        }
19170      "#
19171        .unindent(),
19172    );
19173
19174    cx.update_editor(|editor, window, cx| {
19175        editor.delete_line(&DeleteLine, window, cx);
19176    });
19177    executor.run_until_parked();
19178
19179    cx.assert_state_with_diff(
19180        r#"
19181        use some::mod1;
19182        use some::mod2;
19183
19184        const A: u32 = 42;
19185      + const B: u32 = 42;
19186      + const C: u32 = 42;
19187      + const D: u32 = 42;
19188      + const E: u32 = 42;
19189        ˇ
19190        fn main() {
19191            println!("hello");
19192
19193            println!("world");
19194        }
19195      "#
19196        .unindent(),
19197    );
19198
19199    cx.update_editor(|editor, window, cx| {
19200        editor.move_up(&MoveUp, window, cx);
19201        editor.delete_line(&DeleteLine, window, cx);
19202        editor.move_up(&MoveUp, window, cx);
19203        editor.delete_line(&DeleteLine, window, cx);
19204        editor.move_up(&MoveUp, window, cx);
19205        editor.delete_line(&DeleteLine, window, cx);
19206    });
19207    executor.run_until_parked();
19208    cx.assert_state_with_diff(
19209        r#"
19210        use some::mod1;
19211        use some::mod2;
19212
19213        const A: u32 = 42;
19214      + const B: u32 = 42;
19215        ˇ
19216        fn main() {
19217            println!("hello");
19218
19219            println!("world");
19220        }
19221      "#
19222        .unindent(),
19223    );
19224
19225    cx.update_editor(|editor, window, cx| {
19226        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
19227        editor.delete_line(&DeleteLine, window, cx);
19228    });
19229    executor.run_until_parked();
19230    cx.assert_state_with_diff(
19231        r#"
19232        ˇ
19233        fn main() {
19234            println!("hello");
19235
19236            println!("world");
19237        }
19238      "#
19239        .unindent(),
19240    );
19241}
19242
19243#[gpui::test]
19244async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
19245    init_test(cx, |_| {});
19246
19247    let mut cx = EditorTestContext::new(cx).await;
19248    cx.set_head_text(indoc! { "
19249        one
19250        two
19251        three
19252        four
19253        five
19254        "
19255    });
19256    cx.set_state(indoc! { "
19257        one
19258        ˇthree
19259        five
19260    "});
19261    cx.run_until_parked();
19262    cx.update_editor(|editor, window, cx| {
19263        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19264    });
19265    cx.assert_state_with_diff(
19266        indoc! { "
19267        one
19268      - two
19269        ˇthree
19270      - four
19271        five
19272    "}
19273        .to_string(),
19274    );
19275    cx.update_editor(|editor, window, cx| {
19276        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19277    });
19278
19279    cx.assert_state_with_diff(
19280        indoc! { "
19281        one
19282        ˇthree
19283        five
19284    "}
19285        .to_string(),
19286    );
19287
19288    cx.set_state(indoc! { "
19289        one
19290        ˇTWO
19291        three
19292        four
19293        five
19294    "});
19295    cx.run_until_parked();
19296    cx.update_editor(|editor, window, cx| {
19297        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19298    });
19299
19300    cx.assert_state_with_diff(
19301        indoc! { "
19302            one
19303          - two
19304          + ˇTWO
19305            three
19306            four
19307            five
19308        "}
19309        .to_string(),
19310    );
19311    cx.update_editor(|editor, window, cx| {
19312        editor.move_up(&Default::default(), window, cx);
19313        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19314    });
19315    cx.assert_state_with_diff(
19316        indoc! { "
19317            one
19318            ˇTWO
19319            three
19320            four
19321            five
19322        "}
19323        .to_string(),
19324    );
19325}
19326
19327#[gpui::test]
19328async fn test_edits_around_expanded_deletion_hunks(
19329    executor: BackgroundExecutor,
19330    cx: &mut TestAppContext,
19331) {
19332    init_test(cx, |_| {});
19333
19334    let mut cx = EditorTestContext::new(cx).await;
19335
19336    let diff_base = r#"
19337        use some::mod1;
19338        use some::mod2;
19339
19340        const A: u32 = 42;
19341        const B: u32 = 42;
19342        const C: u32 = 42;
19343
19344
19345        fn main() {
19346            println!("hello");
19347
19348            println!("world");
19349        }
19350    "#
19351    .unindent();
19352    executor.run_until_parked();
19353    cx.set_state(
19354        &r#"
19355        use some::mod1;
19356        use some::mod2;
19357
19358        ˇconst B: u32 = 42;
19359        const C: u32 = 42;
19360
19361
19362        fn main() {
19363            println!("hello");
19364
19365            println!("world");
19366        }
19367        "#
19368        .unindent(),
19369    );
19370
19371    cx.set_head_text(&diff_base);
19372    executor.run_until_parked();
19373
19374    cx.update_editor(|editor, window, cx| {
19375        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19376    });
19377    executor.run_until_parked();
19378
19379    cx.assert_state_with_diff(
19380        r#"
19381        use some::mod1;
19382        use some::mod2;
19383
19384      - const A: u32 = 42;
19385        ˇconst B: u32 = 42;
19386        const C: u32 = 42;
19387
19388
19389        fn main() {
19390            println!("hello");
19391
19392            println!("world");
19393        }
19394      "#
19395        .unindent(),
19396    );
19397
19398    cx.update_editor(|editor, window, cx| {
19399        editor.delete_line(&DeleteLine, window, cx);
19400    });
19401    executor.run_until_parked();
19402    cx.assert_state_with_diff(
19403        r#"
19404        use some::mod1;
19405        use some::mod2;
19406
19407      - const A: u32 = 42;
19408      - const B: u32 = 42;
19409        ˇconst C: u32 = 42;
19410
19411
19412        fn main() {
19413            println!("hello");
19414
19415            println!("world");
19416        }
19417      "#
19418        .unindent(),
19419    );
19420
19421    cx.update_editor(|editor, window, cx| {
19422        editor.delete_line(&DeleteLine, window, cx);
19423    });
19424    executor.run_until_parked();
19425    cx.assert_state_with_diff(
19426        r#"
19427        use some::mod1;
19428        use some::mod2;
19429
19430      - const A: u32 = 42;
19431      - const B: u32 = 42;
19432      - const C: u32 = 42;
19433        ˇ
19434
19435        fn main() {
19436            println!("hello");
19437
19438            println!("world");
19439        }
19440      "#
19441        .unindent(),
19442    );
19443
19444    cx.update_editor(|editor, window, cx| {
19445        editor.handle_input("replacement", window, cx);
19446    });
19447    executor.run_until_parked();
19448    cx.assert_state_with_diff(
19449        r#"
19450        use some::mod1;
19451        use some::mod2;
19452
19453      - const A: u32 = 42;
19454      - const B: u32 = 42;
19455      - const C: u32 = 42;
19456      -
19457      + replacementˇ
19458
19459        fn main() {
19460            println!("hello");
19461
19462            println!("world");
19463        }
19464      "#
19465        .unindent(),
19466    );
19467}
19468
19469#[gpui::test]
19470async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19471    init_test(cx, |_| {});
19472
19473    let mut cx = EditorTestContext::new(cx).await;
19474
19475    let base_text = r#"
19476        one
19477        two
19478        three
19479        four
19480        five
19481    "#
19482    .unindent();
19483    executor.run_until_parked();
19484    cx.set_state(
19485        &r#"
19486        one
19487        two
19488        fˇour
19489        five
19490        "#
19491        .unindent(),
19492    );
19493
19494    cx.set_head_text(&base_text);
19495    executor.run_until_parked();
19496
19497    cx.update_editor(|editor, window, cx| {
19498        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19499    });
19500    executor.run_until_parked();
19501
19502    cx.assert_state_with_diff(
19503        r#"
19504          one
19505          two
19506        - three
19507          fˇour
19508          five
19509        "#
19510        .unindent(),
19511    );
19512
19513    cx.update_editor(|editor, window, cx| {
19514        editor.backspace(&Backspace, window, cx);
19515        editor.backspace(&Backspace, window, cx);
19516    });
19517    executor.run_until_parked();
19518    cx.assert_state_with_diff(
19519        r#"
19520          one
19521          two
19522        - threeˇ
19523        - four
19524        + our
19525          five
19526        "#
19527        .unindent(),
19528    );
19529}
19530
19531#[gpui::test]
19532async fn test_edit_after_expanded_modification_hunk(
19533    executor: BackgroundExecutor,
19534    cx: &mut TestAppContext,
19535) {
19536    init_test(cx, |_| {});
19537
19538    let mut cx = EditorTestContext::new(cx).await;
19539
19540    let diff_base = r#"
19541        use some::mod1;
19542        use some::mod2;
19543
19544        const A: u32 = 42;
19545        const B: u32 = 42;
19546        const C: u32 = 42;
19547        const D: u32 = 42;
19548
19549
19550        fn main() {
19551            println!("hello");
19552
19553            println!("world");
19554        }"#
19555    .unindent();
19556
19557    cx.set_state(
19558        &r#"
19559        use some::mod1;
19560        use some::mod2;
19561
19562        const A: u32 = 42;
19563        const B: u32 = 42;
19564        const C: u32 = 43ˇ
19565        const D: u32 = 42;
19566
19567
19568        fn main() {
19569            println!("hello");
19570
19571            println!("world");
19572        }"#
19573        .unindent(),
19574    );
19575
19576    cx.set_head_text(&diff_base);
19577    executor.run_until_parked();
19578    cx.update_editor(|editor, window, cx| {
19579        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19580    });
19581    executor.run_until_parked();
19582
19583    cx.assert_state_with_diff(
19584        r#"
19585        use some::mod1;
19586        use some::mod2;
19587
19588        const A: u32 = 42;
19589        const B: u32 = 42;
19590      - const C: u32 = 42;
19591      + const C: u32 = 43ˇ
19592        const D: u32 = 42;
19593
19594
19595        fn main() {
19596            println!("hello");
19597
19598            println!("world");
19599        }"#
19600        .unindent(),
19601    );
19602
19603    cx.update_editor(|editor, window, cx| {
19604        editor.handle_input("\nnew_line\n", window, cx);
19605    });
19606    executor.run_until_parked();
19607
19608    cx.assert_state_with_diff(
19609        r#"
19610        use some::mod1;
19611        use some::mod2;
19612
19613        const A: u32 = 42;
19614        const B: u32 = 42;
19615      - const C: u32 = 42;
19616      + const C: u32 = 43
19617      + new_line
19618      + ˇ
19619        const D: u32 = 42;
19620
19621
19622        fn main() {
19623            println!("hello");
19624
19625            println!("world");
19626        }"#
19627        .unindent(),
19628    );
19629}
19630
19631#[gpui::test]
19632async fn test_stage_and_unstage_added_file_hunk(
19633    executor: BackgroundExecutor,
19634    cx: &mut TestAppContext,
19635) {
19636    init_test(cx, |_| {});
19637
19638    let mut cx = EditorTestContext::new(cx).await;
19639    cx.update_editor(|editor, _, cx| {
19640        editor.set_expand_all_diff_hunks(cx);
19641    });
19642
19643    let working_copy = r#"
19644            ˇfn main() {
19645                println!("hello, world!");
19646            }
19647        "#
19648    .unindent();
19649
19650    cx.set_state(&working_copy);
19651    executor.run_until_parked();
19652
19653    cx.assert_state_with_diff(
19654        r#"
19655            + ˇfn main() {
19656            +     println!("hello, world!");
19657            + }
19658        "#
19659        .unindent(),
19660    );
19661    cx.assert_index_text(None);
19662
19663    cx.update_editor(|editor, window, cx| {
19664        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19665    });
19666    executor.run_until_parked();
19667    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
19668    cx.assert_state_with_diff(
19669        r#"
19670            + ˇfn main() {
19671            +     println!("hello, world!");
19672            + }
19673        "#
19674        .unindent(),
19675    );
19676
19677    cx.update_editor(|editor, window, cx| {
19678        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19679    });
19680    executor.run_until_parked();
19681    cx.assert_index_text(None);
19682}
19683
19684async fn setup_indent_guides_editor(
19685    text: &str,
19686    cx: &mut TestAppContext,
19687) -> (BufferId, EditorTestContext) {
19688    init_test(cx, |_| {});
19689
19690    let mut cx = EditorTestContext::new(cx).await;
19691
19692    let buffer_id = cx.update_editor(|editor, window, cx| {
19693        editor.set_text(text, window, cx);
19694        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
19695
19696        buffer_ids[0]
19697    });
19698
19699    (buffer_id, cx)
19700}
19701
19702fn assert_indent_guides(
19703    range: Range<u32>,
19704    expected: Vec<IndentGuide>,
19705    active_indices: Option<Vec<usize>>,
19706    cx: &mut EditorTestContext,
19707) {
19708    let indent_guides = cx.update_editor(|editor, window, cx| {
19709        let snapshot = editor.snapshot(window, cx).display_snapshot;
19710        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
19711            editor,
19712            MultiBufferRow(range.start)..MultiBufferRow(range.end),
19713            true,
19714            &snapshot,
19715            cx,
19716        );
19717
19718        indent_guides.sort_by(|a, b| {
19719            a.depth.cmp(&b.depth).then(
19720                a.start_row
19721                    .cmp(&b.start_row)
19722                    .then(a.end_row.cmp(&b.end_row)),
19723            )
19724        });
19725        indent_guides
19726    });
19727
19728    if let Some(expected) = active_indices {
19729        let active_indices = cx.update_editor(|editor, window, cx| {
19730            let snapshot = editor.snapshot(window, cx).display_snapshot;
19731            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
19732        });
19733
19734        assert_eq!(
19735            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
19736            expected,
19737            "Active indent guide indices do not match"
19738        );
19739    }
19740
19741    assert_eq!(indent_guides, expected, "Indent guides do not match");
19742}
19743
19744fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
19745    IndentGuide {
19746        buffer_id,
19747        start_row: MultiBufferRow(start_row),
19748        end_row: MultiBufferRow(end_row),
19749        depth,
19750        tab_size: 4,
19751        settings: IndentGuideSettings {
19752            enabled: true,
19753            line_width: 1,
19754            active_line_width: 1,
19755            ..Default::default()
19756        },
19757    }
19758}
19759
19760#[gpui::test]
19761async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
19762    let (buffer_id, mut cx) = setup_indent_guides_editor(
19763        &"
19764        fn main() {
19765            let a = 1;
19766        }"
19767        .unindent(),
19768        cx,
19769    )
19770    .await;
19771
19772    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
19773}
19774
19775#[gpui::test]
19776async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
19777    let (buffer_id, mut cx) = setup_indent_guides_editor(
19778        &"
19779        fn main() {
19780            let a = 1;
19781            let b = 2;
19782        }"
19783        .unindent(),
19784        cx,
19785    )
19786    .await;
19787
19788    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
19789}
19790
19791#[gpui::test]
19792async fn test_indent_guide_nested(cx: &mut TestAppContext) {
19793    let (buffer_id, mut cx) = setup_indent_guides_editor(
19794        &"
19795        fn main() {
19796            let a = 1;
19797            if a == 3 {
19798                let b = 2;
19799            } else {
19800                let c = 3;
19801            }
19802        }"
19803        .unindent(),
19804        cx,
19805    )
19806    .await;
19807
19808    assert_indent_guides(
19809        0..8,
19810        vec![
19811            indent_guide(buffer_id, 1, 6, 0),
19812            indent_guide(buffer_id, 3, 3, 1),
19813            indent_guide(buffer_id, 5, 5, 1),
19814        ],
19815        None,
19816        &mut cx,
19817    );
19818}
19819
19820#[gpui::test]
19821async fn test_indent_guide_tab(cx: &mut TestAppContext) {
19822    let (buffer_id, mut cx) = setup_indent_guides_editor(
19823        &"
19824        fn main() {
19825            let a = 1;
19826                let b = 2;
19827            let c = 3;
19828        }"
19829        .unindent(),
19830        cx,
19831    )
19832    .await;
19833
19834    assert_indent_guides(
19835        0..5,
19836        vec![
19837            indent_guide(buffer_id, 1, 3, 0),
19838            indent_guide(buffer_id, 2, 2, 1),
19839        ],
19840        None,
19841        &mut cx,
19842    );
19843}
19844
19845#[gpui::test]
19846async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
19847    let (buffer_id, mut cx) = setup_indent_guides_editor(
19848        &"
19849        fn main() {
19850            let a = 1;
19851
19852            let c = 3;
19853        }"
19854        .unindent(),
19855        cx,
19856    )
19857    .await;
19858
19859    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
19860}
19861
19862#[gpui::test]
19863async fn test_indent_guide_complex(cx: &mut TestAppContext) {
19864    let (buffer_id, mut cx) = setup_indent_guides_editor(
19865        &"
19866        fn main() {
19867            let a = 1;
19868
19869            let c = 3;
19870
19871            if a == 3 {
19872                let b = 2;
19873            } else {
19874                let c = 3;
19875            }
19876        }"
19877        .unindent(),
19878        cx,
19879    )
19880    .await;
19881
19882    assert_indent_guides(
19883        0..11,
19884        vec![
19885            indent_guide(buffer_id, 1, 9, 0),
19886            indent_guide(buffer_id, 6, 6, 1),
19887            indent_guide(buffer_id, 8, 8, 1),
19888        ],
19889        None,
19890        &mut cx,
19891    );
19892}
19893
19894#[gpui::test]
19895async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
19896    let (buffer_id, mut cx) = setup_indent_guides_editor(
19897        &"
19898        fn main() {
19899            let a = 1;
19900
19901            let c = 3;
19902
19903            if a == 3 {
19904                let b = 2;
19905            } else {
19906                let c = 3;
19907            }
19908        }"
19909        .unindent(),
19910        cx,
19911    )
19912    .await;
19913
19914    assert_indent_guides(
19915        1..11,
19916        vec![
19917            indent_guide(buffer_id, 1, 9, 0),
19918            indent_guide(buffer_id, 6, 6, 1),
19919            indent_guide(buffer_id, 8, 8, 1),
19920        ],
19921        None,
19922        &mut cx,
19923    );
19924}
19925
19926#[gpui::test]
19927async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
19928    let (buffer_id, mut cx) = setup_indent_guides_editor(
19929        &"
19930        fn main() {
19931            let a = 1;
19932
19933            let c = 3;
19934
19935            if a == 3 {
19936                let b = 2;
19937            } else {
19938                let c = 3;
19939            }
19940        }"
19941        .unindent(),
19942        cx,
19943    )
19944    .await;
19945
19946    assert_indent_guides(
19947        1..10,
19948        vec![
19949            indent_guide(buffer_id, 1, 9, 0),
19950            indent_guide(buffer_id, 6, 6, 1),
19951            indent_guide(buffer_id, 8, 8, 1),
19952        ],
19953        None,
19954        &mut cx,
19955    );
19956}
19957
19958#[gpui::test]
19959async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
19960    let (buffer_id, mut cx) = setup_indent_guides_editor(
19961        &"
19962        fn main() {
19963            if a {
19964                b(
19965                    c,
19966                    d,
19967                )
19968            } else {
19969                e(
19970                    f
19971                )
19972            }
19973        }"
19974        .unindent(),
19975        cx,
19976    )
19977    .await;
19978
19979    assert_indent_guides(
19980        0..11,
19981        vec![
19982            indent_guide(buffer_id, 1, 10, 0),
19983            indent_guide(buffer_id, 2, 5, 1),
19984            indent_guide(buffer_id, 7, 9, 1),
19985            indent_guide(buffer_id, 3, 4, 2),
19986            indent_guide(buffer_id, 8, 8, 2),
19987        ],
19988        None,
19989        &mut cx,
19990    );
19991
19992    cx.update_editor(|editor, window, cx| {
19993        editor.fold_at(MultiBufferRow(2), window, cx);
19994        assert_eq!(
19995            editor.display_text(cx),
19996            "
19997            fn main() {
19998                if a {
19999                    b(⋯
20000                    )
20001                } else {
20002                    e(
20003                        f
20004                    )
20005                }
20006            }"
20007            .unindent()
20008        );
20009    });
20010
20011    assert_indent_guides(
20012        0..11,
20013        vec![
20014            indent_guide(buffer_id, 1, 10, 0),
20015            indent_guide(buffer_id, 2, 5, 1),
20016            indent_guide(buffer_id, 7, 9, 1),
20017            indent_guide(buffer_id, 8, 8, 2),
20018        ],
20019        None,
20020        &mut cx,
20021    );
20022}
20023
20024#[gpui::test]
20025async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
20026    let (buffer_id, mut cx) = setup_indent_guides_editor(
20027        &"
20028        block1
20029            block2
20030                block3
20031                    block4
20032            block2
20033        block1
20034        block1"
20035            .unindent(),
20036        cx,
20037    )
20038    .await;
20039
20040    assert_indent_guides(
20041        1..10,
20042        vec![
20043            indent_guide(buffer_id, 1, 4, 0),
20044            indent_guide(buffer_id, 2, 3, 1),
20045            indent_guide(buffer_id, 3, 3, 2),
20046        ],
20047        None,
20048        &mut cx,
20049    );
20050}
20051
20052#[gpui::test]
20053async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
20054    let (buffer_id, mut cx) = setup_indent_guides_editor(
20055        &"
20056        block1
20057            block2
20058                block3
20059
20060        block1
20061        block1"
20062            .unindent(),
20063        cx,
20064    )
20065    .await;
20066
20067    assert_indent_guides(
20068        0..6,
20069        vec![
20070            indent_guide(buffer_id, 1, 2, 0),
20071            indent_guide(buffer_id, 2, 2, 1),
20072        ],
20073        None,
20074        &mut cx,
20075    );
20076}
20077
20078#[gpui::test]
20079async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
20080    let (buffer_id, mut cx) = setup_indent_guides_editor(
20081        &"
20082        function component() {
20083        \treturn (
20084        \t\t\t
20085        \t\t<div>
20086        \t\t\t<abc></abc>
20087        \t\t</div>
20088        \t)
20089        }"
20090        .unindent(),
20091        cx,
20092    )
20093    .await;
20094
20095    assert_indent_guides(
20096        0..8,
20097        vec![
20098            indent_guide(buffer_id, 1, 6, 0),
20099            indent_guide(buffer_id, 2, 5, 1),
20100            indent_guide(buffer_id, 4, 4, 2),
20101        ],
20102        None,
20103        &mut cx,
20104    );
20105}
20106
20107#[gpui::test]
20108async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
20109    let (buffer_id, mut cx) = setup_indent_guides_editor(
20110        &"
20111        function component() {
20112        \treturn (
20113        \t
20114        \t\t<div>
20115        \t\t\t<abc></abc>
20116        \t\t</div>
20117        \t)
20118        }"
20119        .unindent(),
20120        cx,
20121    )
20122    .await;
20123
20124    assert_indent_guides(
20125        0..8,
20126        vec![
20127            indent_guide(buffer_id, 1, 6, 0),
20128            indent_guide(buffer_id, 2, 5, 1),
20129            indent_guide(buffer_id, 4, 4, 2),
20130        ],
20131        None,
20132        &mut cx,
20133    );
20134}
20135
20136#[gpui::test]
20137async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
20138    let (buffer_id, mut cx) = setup_indent_guides_editor(
20139        &"
20140        block1
20141
20142
20143
20144            block2
20145        "
20146        .unindent(),
20147        cx,
20148    )
20149    .await;
20150
20151    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20152}
20153
20154#[gpui::test]
20155async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
20156    let (buffer_id, mut cx) = setup_indent_guides_editor(
20157        &"
20158        def a:
20159        \tb = 3
20160        \tif True:
20161        \t\tc = 4
20162        \t\td = 5
20163        \tprint(b)
20164        "
20165        .unindent(),
20166        cx,
20167    )
20168    .await;
20169
20170    assert_indent_guides(
20171        0..6,
20172        vec![
20173            indent_guide(buffer_id, 1, 5, 0),
20174            indent_guide(buffer_id, 3, 4, 1),
20175        ],
20176        None,
20177        &mut cx,
20178    );
20179}
20180
20181#[gpui::test]
20182async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
20183    let (buffer_id, mut cx) = setup_indent_guides_editor(
20184        &"
20185    fn main() {
20186        let a = 1;
20187    }"
20188        .unindent(),
20189        cx,
20190    )
20191    .await;
20192
20193    cx.update_editor(|editor, window, cx| {
20194        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20195            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20196        });
20197    });
20198
20199    assert_indent_guides(
20200        0..3,
20201        vec![indent_guide(buffer_id, 1, 1, 0)],
20202        Some(vec![0]),
20203        &mut cx,
20204    );
20205}
20206
20207#[gpui::test]
20208async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
20209    let (buffer_id, mut cx) = setup_indent_guides_editor(
20210        &"
20211    fn main() {
20212        if 1 == 2 {
20213            let a = 1;
20214        }
20215    }"
20216        .unindent(),
20217        cx,
20218    )
20219    .await;
20220
20221    cx.update_editor(|editor, window, cx| {
20222        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20223            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20224        });
20225    });
20226
20227    assert_indent_guides(
20228        0..4,
20229        vec![
20230            indent_guide(buffer_id, 1, 3, 0),
20231            indent_guide(buffer_id, 2, 2, 1),
20232        ],
20233        Some(vec![1]),
20234        &mut cx,
20235    );
20236
20237    cx.update_editor(|editor, window, cx| {
20238        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20239            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20240        });
20241    });
20242
20243    assert_indent_guides(
20244        0..4,
20245        vec![
20246            indent_guide(buffer_id, 1, 3, 0),
20247            indent_guide(buffer_id, 2, 2, 1),
20248        ],
20249        Some(vec![1]),
20250        &mut cx,
20251    );
20252
20253    cx.update_editor(|editor, window, cx| {
20254        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20255            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
20256        });
20257    });
20258
20259    assert_indent_guides(
20260        0..4,
20261        vec![
20262            indent_guide(buffer_id, 1, 3, 0),
20263            indent_guide(buffer_id, 2, 2, 1),
20264        ],
20265        Some(vec![0]),
20266        &mut cx,
20267    );
20268}
20269
20270#[gpui::test]
20271async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
20272    let (buffer_id, mut cx) = setup_indent_guides_editor(
20273        &"
20274    fn main() {
20275        let a = 1;
20276
20277        let b = 2;
20278    }"
20279        .unindent(),
20280        cx,
20281    )
20282    .await;
20283
20284    cx.update_editor(|editor, window, cx| {
20285        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20286            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20287        });
20288    });
20289
20290    assert_indent_guides(
20291        0..5,
20292        vec![indent_guide(buffer_id, 1, 3, 0)],
20293        Some(vec![0]),
20294        &mut cx,
20295    );
20296}
20297
20298#[gpui::test]
20299async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
20300    let (buffer_id, mut cx) = setup_indent_guides_editor(
20301        &"
20302    def m:
20303        a = 1
20304        pass"
20305            .unindent(),
20306        cx,
20307    )
20308    .await;
20309
20310    cx.update_editor(|editor, window, cx| {
20311        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20312            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20313        });
20314    });
20315
20316    assert_indent_guides(
20317        0..3,
20318        vec![indent_guide(buffer_id, 1, 2, 0)],
20319        Some(vec![0]),
20320        &mut cx,
20321    );
20322}
20323
20324#[gpui::test]
20325async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
20326    init_test(cx, |_| {});
20327    let mut cx = EditorTestContext::new(cx).await;
20328    let text = indoc! {
20329        "
20330        impl A {
20331            fn b() {
20332                0;
20333                3;
20334                5;
20335                6;
20336                7;
20337            }
20338        }
20339        "
20340    };
20341    let base_text = indoc! {
20342        "
20343        impl A {
20344            fn b() {
20345                0;
20346                1;
20347                2;
20348                3;
20349                4;
20350            }
20351            fn c() {
20352                5;
20353                6;
20354                7;
20355            }
20356        }
20357        "
20358    };
20359
20360    cx.update_editor(|editor, window, cx| {
20361        editor.set_text(text, window, cx);
20362
20363        editor.buffer().update(cx, |multibuffer, cx| {
20364            let buffer = multibuffer.as_singleton().unwrap();
20365            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
20366
20367            multibuffer.set_all_diff_hunks_expanded(cx);
20368            multibuffer.add_diff(diff, cx);
20369
20370            buffer.read(cx).remote_id()
20371        })
20372    });
20373    cx.run_until_parked();
20374
20375    cx.assert_state_with_diff(
20376        indoc! { "
20377          impl A {
20378              fn b() {
20379                  0;
20380        -         1;
20381        -         2;
20382                  3;
20383        -         4;
20384        -     }
20385        -     fn c() {
20386                  5;
20387                  6;
20388                  7;
20389              }
20390          }
20391          ˇ"
20392        }
20393        .to_string(),
20394    );
20395
20396    let mut actual_guides = cx.update_editor(|editor, window, cx| {
20397        editor
20398            .snapshot(window, cx)
20399            .buffer_snapshot
20400            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
20401            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
20402            .collect::<Vec<_>>()
20403    });
20404    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
20405    assert_eq!(
20406        actual_guides,
20407        vec![
20408            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
20409            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
20410            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
20411        ]
20412    );
20413}
20414
20415#[gpui::test]
20416async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
20417    init_test(cx, |_| {});
20418    let mut cx = EditorTestContext::new(cx).await;
20419
20420    let diff_base = r#"
20421        a
20422        b
20423        c
20424        "#
20425    .unindent();
20426
20427    cx.set_state(
20428        &r#"
20429        ˇA
20430        b
20431        C
20432        "#
20433        .unindent(),
20434    );
20435    cx.set_head_text(&diff_base);
20436    cx.update_editor(|editor, window, cx| {
20437        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20438    });
20439    executor.run_until_parked();
20440
20441    let both_hunks_expanded = r#"
20442        - a
20443        + ˇA
20444          b
20445        - c
20446        + C
20447        "#
20448    .unindent();
20449
20450    cx.assert_state_with_diff(both_hunks_expanded.clone());
20451
20452    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20453        let snapshot = editor.snapshot(window, cx);
20454        let hunks = editor
20455            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20456            .collect::<Vec<_>>();
20457        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20458        let buffer_id = hunks[0].buffer_id;
20459        hunks
20460            .into_iter()
20461            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20462            .collect::<Vec<_>>()
20463    });
20464    assert_eq!(hunk_ranges.len(), 2);
20465
20466    cx.update_editor(|editor, _, cx| {
20467        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20468    });
20469    executor.run_until_parked();
20470
20471    let second_hunk_expanded = r#"
20472          ˇA
20473          b
20474        - c
20475        + C
20476        "#
20477    .unindent();
20478
20479    cx.assert_state_with_diff(second_hunk_expanded);
20480
20481    cx.update_editor(|editor, _, cx| {
20482        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20483    });
20484    executor.run_until_parked();
20485
20486    cx.assert_state_with_diff(both_hunks_expanded.clone());
20487
20488    cx.update_editor(|editor, _, cx| {
20489        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20490    });
20491    executor.run_until_parked();
20492
20493    let first_hunk_expanded = r#"
20494        - a
20495        + ˇA
20496          b
20497          C
20498        "#
20499    .unindent();
20500
20501    cx.assert_state_with_diff(first_hunk_expanded);
20502
20503    cx.update_editor(|editor, _, cx| {
20504        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20505    });
20506    executor.run_until_parked();
20507
20508    cx.assert_state_with_diff(both_hunks_expanded);
20509
20510    cx.set_state(
20511        &r#"
20512        ˇA
20513        b
20514        "#
20515        .unindent(),
20516    );
20517    cx.run_until_parked();
20518
20519    // TODO this cursor position seems bad
20520    cx.assert_state_with_diff(
20521        r#"
20522        - ˇa
20523        + A
20524          b
20525        "#
20526        .unindent(),
20527    );
20528
20529    cx.update_editor(|editor, window, cx| {
20530        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20531    });
20532
20533    cx.assert_state_with_diff(
20534        r#"
20535            - ˇa
20536            + A
20537              b
20538            - c
20539            "#
20540        .unindent(),
20541    );
20542
20543    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20544        let snapshot = editor.snapshot(window, cx);
20545        let hunks = editor
20546            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20547            .collect::<Vec<_>>();
20548        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20549        let buffer_id = hunks[0].buffer_id;
20550        hunks
20551            .into_iter()
20552            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20553            .collect::<Vec<_>>()
20554    });
20555    assert_eq!(hunk_ranges.len(), 2);
20556
20557    cx.update_editor(|editor, _, cx| {
20558        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20559    });
20560    executor.run_until_parked();
20561
20562    cx.assert_state_with_diff(
20563        r#"
20564        - ˇa
20565        + A
20566          b
20567        "#
20568        .unindent(),
20569    );
20570}
20571
20572#[gpui::test]
20573async fn test_toggle_deletion_hunk_at_start_of_file(
20574    executor: BackgroundExecutor,
20575    cx: &mut TestAppContext,
20576) {
20577    init_test(cx, |_| {});
20578    let mut cx = EditorTestContext::new(cx).await;
20579
20580    let diff_base = r#"
20581        a
20582        b
20583        c
20584        "#
20585    .unindent();
20586
20587    cx.set_state(
20588        &r#"
20589        ˇb
20590        c
20591        "#
20592        .unindent(),
20593    );
20594    cx.set_head_text(&diff_base);
20595    cx.update_editor(|editor, window, cx| {
20596        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20597    });
20598    executor.run_until_parked();
20599
20600    let hunk_expanded = r#"
20601        - a
20602          ˇb
20603          c
20604        "#
20605    .unindent();
20606
20607    cx.assert_state_with_diff(hunk_expanded.clone());
20608
20609    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20610        let snapshot = editor.snapshot(window, cx);
20611        let hunks = editor
20612            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20613            .collect::<Vec<_>>();
20614        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20615        let buffer_id = hunks[0].buffer_id;
20616        hunks
20617            .into_iter()
20618            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20619            .collect::<Vec<_>>()
20620    });
20621    assert_eq!(hunk_ranges.len(), 1);
20622
20623    cx.update_editor(|editor, _, cx| {
20624        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20625    });
20626    executor.run_until_parked();
20627
20628    let hunk_collapsed = r#"
20629          ˇb
20630          c
20631        "#
20632    .unindent();
20633
20634    cx.assert_state_with_diff(hunk_collapsed);
20635
20636    cx.update_editor(|editor, _, cx| {
20637        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20638    });
20639    executor.run_until_parked();
20640
20641    cx.assert_state_with_diff(hunk_expanded);
20642}
20643
20644#[gpui::test]
20645async fn test_display_diff_hunks(cx: &mut TestAppContext) {
20646    init_test(cx, |_| {});
20647
20648    let fs = FakeFs::new(cx.executor());
20649    fs.insert_tree(
20650        path!("/test"),
20651        json!({
20652            ".git": {},
20653            "file-1": "ONE\n",
20654            "file-2": "TWO\n",
20655            "file-3": "THREE\n",
20656        }),
20657    )
20658    .await;
20659
20660    fs.set_head_for_repo(
20661        path!("/test/.git").as_ref(),
20662        &[
20663            ("file-1".into(), "one\n".into()),
20664            ("file-2".into(), "two\n".into()),
20665            ("file-3".into(), "three\n".into()),
20666        ],
20667        "deadbeef",
20668    );
20669
20670    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
20671    let mut buffers = vec![];
20672    for i in 1..=3 {
20673        let buffer = project
20674            .update(cx, |project, cx| {
20675                let path = format!(path!("/test/file-{}"), i);
20676                project.open_local_buffer(path, cx)
20677            })
20678            .await
20679            .unwrap();
20680        buffers.push(buffer);
20681    }
20682
20683    let multibuffer = cx.new(|cx| {
20684        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
20685        multibuffer.set_all_diff_hunks_expanded(cx);
20686        for buffer in &buffers {
20687            let snapshot = buffer.read(cx).snapshot();
20688            multibuffer.set_excerpts_for_path(
20689                PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
20690                buffer.clone(),
20691                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
20692                2,
20693                cx,
20694            );
20695        }
20696        multibuffer
20697    });
20698
20699    let editor = cx.add_window(|window, cx| {
20700        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
20701    });
20702    cx.run_until_parked();
20703
20704    let snapshot = editor
20705        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
20706        .unwrap();
20707    let hunks = snapshot
20708        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
20709        .map(|hunk| match hunk {
20710            DisplayDiffHunk::Unfolded {
20711                display_row_range, ..
20712            } => display_row_range,
20713            DisplayDiffHunk::Folded { .. } => unreachable!(),
20714        })
20715        .collect::<Vec<_>>();
20716    assert_eq!(
20717        hunks,
20718        [
20719            DisplayRow(2)..DisplayRow(4),
20720            DisplayRow(7)..DisplayRow(9),
20721            DisplayRow(12)..DisplayRow(14),
20722        ]
20723    );
20724}
20725
20726#[gpui::test]
20727async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
20728    init_test(cx, |_| {});
20729
20730    let mut cx = EditorTestContext::new(cx).await;
20731    cx.set_head_text(indoc! { "
20732        one
20733        two
20734        three
20735        four
20736        five
20737        "
20738    });
20739    cx.set_index_text(indoc! { "
20740        one
20741        two
20742        three
20743        four
20744        five
20745        "
20746    });
20747    cx.set_state(indoc! {"
20748        one
20749        TWO
20750        ˇTHREE
20751        FOUR
20752        five
20753    "});
20754    cx.run_until_parked();
20755    cx.update_editor(|editor, window, cx| {
20756        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20757    });
20758    cx.run_until_parked();
20759    cx.assert_index_text(Some(indoc! {"
20760        one
20761        TWO
20762        THREE
20763        FOUR
20764        five
20765    "}));
20766    cx.set_state(indoc! { "
20767        one
20768        TWO
20769        ˇTHREE-HUNDRED
20770        FOUR
20771        five
20772    "});
20773    cx.run_until_parked();
20774    cx.update_editor(|editor, window, cx| {
20775        let snapshot = editor.snapshot(window, cx);
20776        let hunks = editor
20777            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20778            .collect::<Vec<_>>();
20779        assert_eq!(hunks.len(), 1);
20780        assert_eq!(
20781            hunks[0].status(),
20782            DiffHunkStatus {
20783                kind: DiffHunkStatusKind::Modified,
20784                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
20785            }
20786        );
20787
20788        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20789    });
20790    cx.run_until_parked();
20791    cx.assert_index_text(Some(indoc! {"
20792        one
20793        TWO
20794        THREE-HUNDRED
20795        FOUR
20796        five
20797    "}));
20798}
20799
20800#[gpui::test]
20801fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
20802    init_test(cx, |_| {});
20803
20804    let editor = cx.add_window(|window, cx| {
20805        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
20806        build_editor(buffer, window, cx)
20807    });
20808
20809    let render_args = Arc::new(Mutex::new(None));
20810    let snapshot = editor
20811        .update(cx, |editor, window, cx| {
20812            let snapshot = editor.buffer().read(cx).snapshot(cx);
20813            let range =
20814                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
20815
20816            struct RenderArgs {
20817                row: MultiBufferRow,
20818                folded: bool,
20819                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
20820            }
20821
20822            let crease = Crease::inline(
20823                range,
20824                FoldPlaceholder::test(),
20825                {
20826                    let toggle_callback = render_args.clone();
20827                    move |row, folded, callback, _window, _cx| {
20828                        *toggle_callback.lock() = Some(RenderArgs {
20829                            row,
20830                            folded,
20831                            callback,
20832                        });
20833                        div()
20834                    }
20835                },
20836                |_row, _folded, _window, _cx| div(),
20837            );
20838
20839            editor.insert_creases(Some(crease), cx);
20840            let snapshot = editor.snapshot(window, cx);
20841            let _div =
20842                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
20843            snapshot
20844        })
20845        .unwrap();
20846
20847    let render_args = render_args.lock().take().unwrap();
20848    assert_eq!(render_args.row, MultiBufferRow(1));
20849    assert!(!render_args.folded);
20850    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
20851
20852    cx.update_window(*editor, |_, window, cx| {
20853        (render_args.callback)(true, window, cx)
20854    })
20855    .unwrap();
20856    let snapshot = editor
20857        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
20858        .unwrap();
20859    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
20860
20861    cx.update_window(*editor, |_, window, cx| {
20862        (render_args.callback)(false, window, cx)
20863    })
20864    .unwrap();
20865    let snapshot = editor
20866        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
20867        .unwrap();
20868    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
20869}
20870
20871#[gpui::test]
20872async fn test_input_text(cx: &mut TestAppContext) {
20873    init_test(cx, |_| {});
20874    let mut cx = EditorTestContext::new(cx).await;
20875
20876    cx.set_state(
20877        &r#"ˇone
20878        two
20879
20880        three
20881        fourˇ
20882        five
20883
20884        siˇx"#
20885            .unindent(),
20886    );
20887
20888    cx.dispatch_action(HandleInput(String::new()));
20889    cx.assert_editor_state(
20890        &r#"ˇone
20891        two
20892
20893        three
20894        fourˇ
20895        five
20896
20897        siˇx"#
20898            .unindent(),
20899    );
20900
20901    cx.dispatch_action(HandleInput("AAAA".to_string()));
20902    cx.assert_editor_state(
20903        &r#"AAAAˇone
20904        two
20905
20906        three
20907        fourAAAAˇ
20908        five
20909
20910        siAAAAˇx"#
20911            .unindent(),
20912    );
20913}
20914
20915#[gpui::test]
20916async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
20917    init_test(cx, |_| {});
20918
20919    let mut cx = EditorTestContext::new(cx).await;
20920    cx.set_state(
20921        r#"let foo = 1;
20922let foo = 2;
20923let foo = 3;
20924let fooˇ = 4;
20925let foo = 5;
20926let foo = 6;
20927let foo = 7;
20928let foo = 8;
20929let foo = 9;
20930let foo = 10;
20931let foo = 11;
20932let foo = 12;
20933let foo = 13;
20934let foo = 14;
20935let foo = 15;"#,
20936    );
20937
20938    cx.update_editor(|e, window, cx| {
20939        assert_eq!(
20940            e.next_scroll_position,
20941            NextScrollCursorCenterTopBottom::Center,
20942            "Default next scroll direction is center",
20943        );
20944
20945        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
20946        assert_eq!(
20947            e.next_scroll_position,
20948            NextScrollCursorCenterTopBottom::Top,
20949            "After center, next scroll direction should be top",
20950        );
20951
20952        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
20953        assert_eq!(
20954            e.next_scroll_position,
20955            NextScrollCursorCenterTopBottom::Bottom,
20956            "After top, next scroll direction should be bottom",
20957        );
20958
20959        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
20960        assert_eq!(
20961            e.next_scroll_position,
20962            NextScrollCursorCenterTopBottom::Center,
20963            "After bottom, scrolling should start over",
20964        );
20965
20966        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
20967        assert_eq!(
20968            e.next_scroll_position,
20969            NextScrollCursorCenterTopBottom::Top,
20970            "Scrolling continues if retriggered fast enough"
20971        );
20972    });
20973
20974    cx.executor()
20975        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
20976    cx.executor().run_until_parked();
20977    cx.update_editor(|e, _, _| {
20978        assert_eq!(
20979            e.next_scroll_position,
20980            NextScrollCursorCenterTopBottom::Center,
20981            "If scrolling is not triggered fast enough, it should reset"
20982        );
20983    });
20984}
20985
20986#[gpui::test]
20987async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
20988    init_test(cx, |_| {});
20989    let mut cx = EditorLspTestContext::new_rust(
20990        lsp::ServerCapabilities {
20991            definition_provider: Some(lsp::OneOf::Left(true)),
20992            references_provider: Some(lsp::OneOf::Left(true)),
20993            ..lsp::ServerCapabilities::default()
20994        },
20995        cx,
20996    )
20997    .await;
20998
20999    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
21000        let go_to_definition = cx
21001            .lsp
21002            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21003                move |params, _| async move {
21004                    if empty_go_to_definition {
21005                        Ok(None)
21006                    } else {
21007                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
21008                            uri: params.text_document_position_params.text_document.uri,
21009                            range: lsp::Range::new(
21010                                lsp::Position::new(4, 3),
21011                                lsp::Position::new(4, 6),
21012                            ),
21013                        })))
21014                    }
21015                },
21016            );
21017        let references = cx
21018            .lsp
21019            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21020                Ok(Some(vec![lsp::Location {
21021                    uri: params.text_document_position.text_document.uri,
21022                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
21023                }]))
21024            });
21025        (go_to_definition, references)
21026    };
21027
21028    cx.set_state(
21029        &r#"fn one() {
21030            let mut a = ˇtwo();
21031        }
21032
21033        fn two() {}"#
21034            .unindent(),
21035    );
21036    set_up_lsp_handlers(false, &mut cx);
21037    let navigated = cx
21038        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21039        .await
21040        .expect("Failed to navigate to definition");
21041    assert_eq!(
21042        navigated,
21043        Navigated::Yes,
21044        "Should have navigated to definition from the GetDefinition response"
21045    );
21046    cx.assert_editor_state(
21047        &r#"fn one() {
21048            let mut a = two();
21049        }
21050
21051        fn «twoˇ»() {}"#
21052            .unindent(),
21053    );
21054
21055    let editors = cx.update_workspace(|workspace, _, cx| {
21056        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21057    });
21058    cx.update_editor(|_, _, test_editor_cx| {
21059        assert_eq!(
21060            editors.len(),
21061            1,
21062            "Initially, only one, test, editor should be open in the workspace"
21063        );
21064        assert_eq!(
21065            test_editor_cx.entity(),
21066            editors.last().expect("Asserted len is 1").clone()
21067        );
21068    });
21069
21070    set_up_lsp_handlers(true, &mut cx);
21071    let navigated = cx
21072        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21073        .await
21074        .expect("Failed to navigate to lookup references");
21075    assert_eq!(
21076        navigated,
21077        Navigated::Yes,
21078        "Should have navigated to references as a fallback after empty GoToDefinition response"
21079    );
21080    // We should not change the selections in the existing file,
21081    // if opening another milti buffer with the references
21082    cx.assert_editor_state(
21083        &r#"fn one() {
21084            let mut a = two();
21085        }
21086
21087        fn «twoˇ»() {}"#
21088            .unindent(),
21089    );
21090    let editors = cx.update_workspace(|workspace, _, cx| {
21091        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21092    });
21093    cx.update_editor(|_, _, test_editor_cx| {
21094        assert_eq!(
21095            editors.len(),
21096            2,
21097            "After falling back to references search, we open a new editor with the results"
21098        );
21099        let references_fallback_text = editors
21100            .into_iter()
21101            .find(|new_editor| *new_editor != test_editor_cx.entity())
21102            .expect("Should have one non-test editor now")
21103            .read(test_editor_cx)
21104            .text(test_editor_cx);
21105        assert_eq!(
21106            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
21107            "Should use the range from the references response and not the GoToDefinition one"
21108        );
21109    });
21110}
21111
21112#[gpui::test]
21113async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
21114    init_test(cx, |_| {});
21115    cx.update(|cx| {
21116        let mut editor_settings = EditorSettings::get_global(cx).clone();
21117        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
21118        EditorSettings::override_global(editor_settings, cx);
21119    });
21120    let mut cx = EditorLspTestContext::new_rust(
21121        lsp::ServerCapabilities {
21122            definition_provider: Some(lsp::OneOf::Left(true)),
21123            references_provider: Some(lsp::OneOf::Left(true)),
21124            ..lsp::ServerCapabilities::default()
21125        },
21126        cx,
21127    )
21128    .await;
21129    let original_state = r#"fn one() {
21130        let mut a = ˇtwo();
21131    }
21132
21133    fn two() {}"#
21134        .unindent();
21135    cx.set_state(&original_state);
21136
21137    let mut go_to_definition = cx
21138        .lsp
21139        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21140            move |_, _| async move { Ok(None) },
21141        );
21142    let _references = cx
21143        .lsp
21144        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
21145            panic!("Should not call for references with no go to definition fallback")
21146        });
21147
21148    let navigated = cx
21149        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21150        .await
21151        .expect("Failed to navigate to lookup references");
21152    go_to_definition
21153        .next()
21154        .await
21155        .expect("Should have called the go_to_definition handler");
21156
21157    assert_eq!(
21158        navigated,
21159        Navigated::No,
21160        "Should have navigated to references as a fallback after empty GoToDefinition response"
21161    );
21162    cx.assert_editor_state(&original_state);
21163    let editors = cx.update_workspace(|workspace, _, cx| {
21164        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21165    });
21166    cx.update_editor(|_, _, _| {
21167        assert_eq!(
21168            editors.len(),
21169            1,
21170            "After unsuccessful fallback, no other editor should have been opened"
21171        );
21172    });
21173}
21174
21175#[gpui::test]
21176async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
21177    init_test(cx, |_| {});
21178
21179    let language = Arc::new(Language::new(
21180        LanguageConfig::default(),
21181        Some(tree_sitter_rust::LANGUAGE.into()),
21182    ));
21183
21184    let text = r#"
21185        #[cfg(test)]
21186        mod tests() {
21187            #[test]
21188            fn runnable_1() {
21189                let a = 1;
21190            }
21191
21192            #[test]
21193            fn runnable_2() {
21194                let a = 1;
21195                let b = 2;
21196            }
21197        }
21198    "#
21199    .unindent();
21200
21201    let fs = FakeFs::new(cx.executor());
21202    fs.insert_file("/file.rs", Default::default()).await;
21203
21204    let project = Project::test(fs, ["/a".as_ref()], cx).await;
21205    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21206    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21207    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
21208    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
21209
21210    let editor = cx.new_window_entity(|window, cx| {
21211        Editor::new(
21212            EditorMode::full(),
21213            multi_buffer,
21214            Some(project.clone()),
21215            window,
21216            cx,
21217        )
21218    });
21219
21220    editor.update_in(cx, |editor, window, cx| {
21221        let snapshot = editor.buffer().read(cx).snapshot(cx);
21222        editor.tasks.insert(
21223            (buffer.read(cx).remote_id(), 3),
21224            RunnableTasks {
21225                templates: vec![],
21226                offset: snapshot.anchor_before(43),
21227                column: 0,
21228                extra_variables: HashMap::default(),
21229                context_range: BufferOffset(43)..BufferOffset(85),
21230            },
21231        );
21232        editor.tasks.insert(
21233            (buffer.read(cx).remote_id(), 8),
21234            RunnableTasks {
21235                templates: vec![],
21236                offset: snapshot.anchor_before(86),
21237                column: 0,
21238                extra_variables: HashMap::default(),
21239                context_range: BufferOffset(86)..BufferOffset(191),
21240            },
21241        );
21242
21243        // Test finding task when cursor is inside function body
21244        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21245            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
21246        });
21247        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
21248        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
21249
21250        // Test finding task when cursor is on function name
21251        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21252            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
21253        });
21254        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
21255        assert_eq!(row, 8, "Should find task when cursor is on function name");
21256    });
21257}
21258
21259#[gpui::test]
21260async fn test_folding_buffers(cx: &mut TestAppContext) {
21261    init_test(cx, |_| {});
21262
21263    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
21264    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
21265    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
21266
21267    let fs = FakeFs::new(cx.executor());
21268    fs.insert_tree(
21269        path!("/a"),
21270        json!({
21271            "first.rs": sample_text_1,
21272            "second.rs": sample_text_2,
21273            "third.rs": sample_text_3,
21274        }),
21275    )
21276    .await;
21277    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21278    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21279    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21280    let worktree = project.update(cx, |project, cx| {
21281        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
21282        assert_eq!(worktrees.len(), 1);
21283        worktrees.pop().unwrap()
21284    });
21285    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
21286
21287    let buffer_1 = project
21288        .update(cx, |project, cx| {
21289            project.open_buffer((worktree_id, "first.rs"), cx)
21290        })
21291        .await
21292        .unwrap();
21293    let buffer_2 = project
21294        .update(cx, |project, cx| {
21295            project.open_buffer((worktree_id, "second.rs"), cx)
21296        })
21297        .await
21298        .unwrap();
21299    let buffer_3 = project
21300        .update(cx, |project, cx| {
21301            project.open_buffer((worktree_id, "third.rs"), cx)
21302        })
21303        .await
21304        .unwrap();
21305
21306    let multi_buffer = cx.new(|cx| {
21307        let mut multi_buffer = MultiBuffer::new(ReadWrite);
21308        multi_buffer.push_excerpts(
21309            buffer_1.clone(),
21310            [
21311                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21312                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21313                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21314            ],
21315            cx,
21316        );
21317        multi_buffer.push_excerpts(
21318            buffer_2.clone(),
21319            [
21320                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21321                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21322                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21323            ],
21324            cx,
21325        );
21326        multi_buffer.push_excerpts(
21327            buffer_3.clone(),
21328            [
21329                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21330                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21331                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21332            ],
21333            cx,
21334        );
21335        multi_buffer
21336    });
21337    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
21338        Editor::new(
21339            EditorMode::full(),
21340            multi_buffer.clone(),
21341            Some(project.clone()),
21342            window,
21343            cx,
21344        )
21345    });
21346
21347    assert_eq!(
21348        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21349        "\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",
21350    );
21351
21352    multi_buffer_editor.update(cx, |editor, cx| {
21353        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
21354    });
21355    assert_eq!(
21356        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21357        "\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",
21358        "After folding the first buffer, its text should not be displayed"
21359    );
21360
21361    multi_buffer_editor.update(cx, |editor, cx| {
21362        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
21363    });
21364    assert_eq!(
21365        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21366        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
21367        "After folding the second buffer, its text should not be displayed"
21368    );
21369
21370    multi_buffer_editor.update(cx, |editor, cx| {
21371        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
21372    });
21373    assert_eq!(
21374        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21375        "\n\n\n\n\n",
21376        "After folding the third buffer, its text should not be displayed"
21377    );
21378
21379    // Emulate selection inside the fold logic, that should work
21380    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21381        editor
21382            .snapshot(window, cx)
21383            .next_line_boundary(Point::new(0, 4));
21384    });
21385
21386    multi_buffer_editor.update(cx, |editor, cx| {
21387        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
21388    });
21389    assert_eq!(
21390        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21391        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
21392        "After unfolding the second buffer, its text should be displayed"
21393    );
21394
21395    // Typing inside of buffer 1 causes that buffer to be unfolded.
21396    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21397        assert_eq!(
21398            multi_buffer
21399                .read(cx)
21400                .snapshot(cx)
21401                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
21402                .collect::<String>(),
21403            "bbbb"
21404        );
21405        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21406            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
21407        });
21408        editor.handle_input("B", window, cx);
21409    });
21410
21411    assert_eq!(
21412        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21413        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
21414        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
21415    );
21416
21417    multi_buffer_editor.update(cx, |editor, cx| {
21418        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
21419    });
21420    assert_eq!(
21421        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21422        "\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",
21423        "After unfolding the all buffers, all original text should be displayed"
21424    );
21425}
21426
21427#[gpui::test]
21428async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
21429    init_test(cx, |_| {});
21430
21431    let sample_text_1 = "1111\n2222\n3333".to_string();
21432    let sample_text_2 = "4444\n5555\n6666".to_string();
21433    let sample_text_3 = "7777\n8888\n9999".to_string();
21434
21435    let fs = FakeFs::new(cx.executor());
21436    fs.insert_tree(
21437        path!("/a"),
21438        json!({
21439            "first.rs": sample_text_1,
21440            "second.rs": sample_text_2,
21441            "third.rs": sample_text_3,
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    let worktree = project.update(cx, |project, cx| {
21449        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
21450        assert_eq!(worktrees.len(), 1);
21451        worktrees.pop().unwrap()
21452    });
21453    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
21454
21455    let buffer_1 = project
21456        .update(cx, |project, cx| {
21457            project.open_buffer((worktree_id, "first.rs"), cx)
21458        })
21459        .await
21460        .unwrap();
21461    let buffer_2 = project
21462        .update(cx, |project, cx| {
21463            project.open_buffer((worktree_id, "second.rs"), cx)
21464        })
21465        .await
21466        .unwrap();
21467    let buffer_3 = project
21468        .update(cx, |project, cx| {
21469            project.open_buffer((worktree_id, "third.rs"), cx)
21470        })
21471        .await
21472        .unwrap();
21473
21474    let multi_buffer = cx.new(|cx| {
21475        let mut multi_buffer = MultiBuffer::new(ReadWrite);
21476        multi_buffer.push_excerpts(
21477            buffer_1.clone(),
21478            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21479            cx,
21480        );
21481        multi_buffer.push_excerpts(
21482            buffer_2.clone(),
21483            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21484            cx,
21485        );
21486        multi_buffer.push_excerpts(
21487            buffer_3.clone(),
21488            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21489            cx,
21490        );
21491        multi_buffer
21492    });
21493
21494    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
21495        Editor::new(
21496            EditorMode::full(),
21497            multi_buffer,
21498            Some(project.clone()),
21499            window,
21500            cx,
21501        )
21502    });
21503
21504    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
21505    assert_eq!(
21506        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21507        full_text,
21508    );
21509
21510    multi_buffer_editor.update(cx, |editor, cx| {
21511        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
21512    });
21513    assert_eq!(
21514        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21515        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
21516        "After folding the first buffer, its text should not be displayed"
21517    );
21518
21519    multi_buffer_editor.update(cx, |editor, cx| {
21520        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
21521    });
21522
21523    assert_eq!(
21524        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21525        "\n\n\n\n\n\n7777\n8888\n9999",
21526        "After folding the second buffer, its text should not be displayed"
21527    );
21528
21529    multi_buffer_editor.update(cx, |editor, cx| {
21530        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
21531    });
21532    assert_eq!(
21533        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21534        "\n\n\n\n\n",
21535        "After folding the third buffer, its text should not be displayed"
21536    );
21537
21538    multi_buffer_editor.update(cx, |editor, cx| {
21539        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
21540    });
21541    assert_eq!(
21542        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21543        "\n\n\n\n4444\n5555\n6666\n\n",
21544        "After unfolding the second buffer, its text should be displayed"
21545    );
21546
21547    multi_buffer_editor.update(cx, |editor, cx| {
21548        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
21549    });
21550    assert_eq!(
21551        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21552        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
21553        "After unfolding the first buffer, its text should be displayed"
21554    );
21555
21556    multi_buffer_editor.update(cx, |editor, cx| {
21557        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
21558    });
21559    assert_eq!(
21560        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21561        full_text,
21562        "After unfolding all buffers, all original text should be displayed"
21563    );
21564}
21565
21566#[gpui::test]
21567async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
21568    init_test(cx, |_| {});
21569
21570    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
21571
21572    let fs = FakeFs::new(cx.executor());
21573    fs.insert_tree(
21574        path!("/a"),
21575        json!({
21576            "main.rs": sample_text,
21577        }),
21578    )
21579    .await;
21580    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21581    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21582    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21583    let worktree = project.update(cx, |project, cx| {
21584        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
21585        assert_eq!(worktrees.len(), 1);
21586        worktrees.pop().unwrap()
21587    });
21588    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
21589
21590    let buffer_1 = project
21591        .update(cx, |project, cx| {
21592            project.open_buffer((worktree_id, "main.rs"), cx)
21593        })
21594        .await
21595        .unwrap();
21596
21597    let multi_buffer = cx.new(|cx| {
21598        let mut multi_buffer = MultiBuffer::new(ReadWrite);
21599        multi_buffer.push_excerpts(
21600            buffer_1.clone(),
21601            [ExcerptRange::new(
21602                Point::new(0, 0)
21603                    ..Point::new(
21604                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
21605                        0,
21606                    ),
21607            )],
21608            cx,
21609        );
21610        multi_buffer
21611    });
21612    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
21613        Editor::new(
21614            EditorMode::full(),
21615            multi_buffer,
21616            Some(project.clone()),
21617            window,
21618            cx,
21619        )
21620    });
21621
21622    let selection_range = Point::new(1, 0)..Point::new(2, 0);
21623    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21624        enum TestHighlight {}
21625        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
21626        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
21627        editor.highlight_text::<TestHighlight>(
21628            vec![highlight_range.clone()],
21629            HighlightStyle::color(Hsla::green()),
21630            cx,
21631        );
21632        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21633            s.select_ranges(Some(highlight_range))
21634        });
21635    });
21636
21637    let full_text = format!("\n\n{sample_text}");
21638    assert_eq!(
21639        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21640        full_text,
21641    );
21642}
21643
21644#[gpui::test]
21645async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
21646    init_test(cx, |_| {});
21647    cx.update(|cx| {
21648        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
21649            "keymaps/default-linux.json",
21650            cx,
21651        )
21652        .unwrap();
21653        cx.bind_keys(default_key_bindings);
21654    });
21655
21656    let (editor, cx) = cx.add_window_view(|window, cx| {
21657        let multi_buffer = MultiBuffer::build_multi(
21658            [
21659                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
21660                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
21661                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
21662                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
21663            ],
21664            cx,
21665        );
21666        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
21667
21668        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
21669        // fold all but the second buffer, so that we test navigating between two
21670        // adjacent folded buffers, as well as folded buffers at the start and
21671        // end the multibuffer
21672        editor.fold_buffer(buffer_ids[0], cx);
21673        editor.fold_buffer(buffer_ids[2], cx);
21674        editor.fold_buffer(buffer_ids[3], cx);
21675
21676        editor
21677    });
21678    cx.simulate_resize(size(px(1000.), px(1000.)));
21679
21680    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
21681    cx.assert_excerpts_with_selections(indoc! {"
21682        [EXCERPT]
21683        ˇ[FOLDED]
21684        [EXCERPT]
21685        a1
21686        b1
21687        [EXCERPT]
21688        [FOLDED]
21689        [EXCERPT]
21690        [FOLDED]
21691        "
21692    });
21693    cx.simulate_keystroke("down");
21694    cx.assert_excerpts_with_selections(indoc! {"
21695        [EXCERPT]
21696        [FOLDED]
21697        [EXCERPT]
21698        ˇa1
21699        b1
21700        [EXCERPT]
21701        [FOLDED]
21702        [EXCERPT]
21703        [FOLDED]
21704        "
21705    });
21706    cx.simulate_keystroke("down");
21707    cx.assert_excerpts_with_selections(indoc! {"
21708        [EXCERPT]
21709        [FOLDED]
21710        [EXCERPT]
21711        a1
21712        ˇb1
21713        [EXCERPT]
21714        [FOLDED]
21715        [EXCERPT]
21716        [FOLDED]
21717        "
21718    });
21719    cx.simulate_keystroke("down");
21720    cx.assert_excerpts_with_selections(indoc! {"
21721        [EXCERPT]
21722        [FOLDED]
21723        [EXCERPT]
21724        a1
21725        b1
21726        ˇ[EXCERPT]
21727        [FOLDED]
21728        [EXCERPT]
21729        [FOLDED]
21730        "
21731    });
21732    cx.simulate_keystroke("down");
21733    cx.assert_excerpts_with_selections(indoc! {"
21734        [EXCERPT]
21735        [FOLDED]
21736        [EXCERPT]
21737        a1
21738        b1
21739        [EXCERPT]
21740        ˇ[FOLDED]
21741        [EXCERPT]
21742        [FOLDED]
21743        "
21744    });
21745    for _ in 0..5 {
21746        cx.simulate_keystroke("down");
21747        cx.assert_excerpts_with_selections(indoc! {"
21748            [EXCERPT]
21749            [FOLDED]
21750            [EXCERPT]
21751            a1
21752            b1
21753            [EXCERPT]
21754            [FOLDED]
21755            [EXCERPT]
21756            ˇ[FOLDED]
21757            "
21758        });
21759    }
21760
21761    cx.simulate_keystroke("up");
21762    cx.assert_excerpts_with_selections(indoc! {"
21763        [EXCERPT]
21764        [FOLDED]
21765        [EXCERPT]
21766        a1
21767        b1
21768        [EXCERPT]
21769        ˇ[FOLDED]
21770        [EXCERPT]
21771        [FOLDED]
21772        "
21773    });
21774    cx.simulate_keystroke("up");
21775    cx.assert_excerpts_with_selections(indoc! {"
21776        [EXCERPT]
21777        [FOLDED]
21778        [EXCERPT]
21779        a1
21780        b1
21781        ˇ[EXCERPT]
21782        [FOLDED]
21783        [EXCERPT]
21784        [FOLDED]
21785        "
21786    });
21787    cx.simulate_keystroke("up");
21788    cx.assert_excerpts_with_selections(indoc! {"
21789        [EXCERPT]
21790        [FOLDED]
21791        [EXCERPT]
21792        a1
21793        ˇb1
21794        [EXCERPT]
21795        [FOLDED]
21796        [EXCERPT]
21797        [FOLDED]
21798        "
21799    });
21800    cx.simulate_keystroke("up");
21801    cx.assert_excerpts_with_selections(indoc! {"
21802        [EXCERPT]
21803        [FOLDED]
21804        [EXCERPT]
21805        ˇa1
21806        b1
21807        [EXCERPT]
21808        [FOLDED]
21809        [EXCERPT]
21810        [FOLDED]
21811        "
21812    });
21813    for _ in 0..5 {
21814        cx.simulate_keystroke("up");
21815        cx.assert_excerpts_with_selections(indoc! {"
21816            [EXCERPT]
21817            ˇ[FOLDED]
21818            [EXCERPT]
21819            a1
21820            b1
21821            [EXCERPT]
21822            [FOLDED]
21823            [EXCERPT]
21824            [FOLDED]
21825            "
21826        });
21827    }
21828}
21829
21830#[gpui::test]
21831async fn test_edit_prediction_text(cx: &mut TestAppContext) {
21832    init_test(cx, |_| {});
21833
21834    // Simple insertion
21835    assert_highlighted_edits(
21836        "Hello, world!",
21837        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
21838        true,
21839        cx,
21840        |highlighted_edits, cx| {
21841            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
21842            assert_eq!(highlighted_edits.highlights.len(), 1);
21843            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
21844            assert_eq!(
21845                highlighted_edits.highlights[0].1.background_color,
21846                Some(cx.theme().status().created_background)
21847            );
21848        },
21849    )
21850    .await;
21851
21852    // Replacement
21853    assert_highlighted_edits(
21854        "This is a test.",
21855        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
21856        false,
21857        cx,
21858        |highlighted_edits, cx| {
21859            assert_eq!(highlighted_edits.text, "That is a test.");
21860            assert_eq!(highlighted_edits.highlights.len(), 1);
21861            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
21862            assert_eq!(
21863                highlighted_edits.highlights[0].1.background_color,
21864                Some(cx.theme().status().created_background)
21865            );
21866        },
21867    )
21868    .await;
21869
21870    // Multiple edits
21871    assert_highlighted_edits(
21872        "Hello, world!",
21873        vec![
21874            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
21875            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
21876        ],
21877        false,
21878        cx,
21879        |highlighted_edits, cx| {
21880            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
21881            assert_eq!(highlighted_edits.highlights.len(), 2);
21882            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
21883            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
21884            assert_eq!(
21885                highlighted_edits.highlights[0].1.background_color,
21886                Some(cx.theme().status().created_background)
21887            );
21888            assert_eq!(
21889                highlighted_edits.highlights[1].1.background_color,
21890                Some(cx.theme().status().created_background)
21891            );
21892        },
21893    )
21894    .await;
21895
21896    // Multiple lines with edits
21897    assert_highlighted_edits(
21898        "First line\nSecond line\nThird line\nFourth line",
21899        vec![
21900            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
21901            (
21902                Point::new(2, 0)..Point::new(2, 10),
21903                "New third line".to_string(),
21904            ),
21905            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
21906        ],
21907        false,
21908        cx,
21909        |highlighted_edits, cx| {
21910            assert_eq!(
21911                highlighted_edits.text,
21912                "Second modified\nNew third line\nFourth updated line"
21913            );
21914            assert_eq!(highlighted_edits.highlights.len(), 3);
21915            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
21916            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
21917            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
21918            for highlight in &highlighted_edits.highlights {
21919                assert_eq!(
21920                    highlight.1.background_color,
21921                    Some(cx.theme().status().created_background)
21922                );
21923            }
21924        },
21925    )
21926    .await;
21927}
21928
21929#[gpui::test]
21930async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
21931    init_test(cx, |_| {});
21932
21933    // Deletion
21934    assert_highlighted_edits(
21935        "Hello, world!",
21936        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
21937        true,
21938        cx,
21939        |highlighted_edits, cx| {
21940            assert_eq!(highlighted_edits.text, "Hello, world!");
21941            assert_eq!(highlighted_edits.highlights.len(), 1);
21942            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
21943            assert_eq!(
21944                highlighted_edits.highlights[0].1.background_color,
21945                Some(cx.theme().status().deleted_background)
21946            );
21947        },
21948    )
21949    .await;
21950
21951    // Insertion
21952    assert_highlighted_edits(
21953        "Hello, world!",
21954        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
21955        true,
21956        cx,
21957        |highlighted_edits, cx| {
21958            assert_eq!(highlighted_edits.highlights.len(), 1);
21959            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
21960            assert_eq!(
21961                highlighted_edits.highlights[0].1.background_color,
21962                Some(cx.theme().status().created_background)
21963            );
21964        },
21965    )
21966    .await;
21967}
21968
21969async fn assert_highlighted_edits(
21970    text: &str,
21971    edits: Vec<(Range<Point>, String)>,
21972    include_deletions: bool,
21973    cx: &mut TestAppContext,
21974    assertion_fn: impl Fn(HighlightedText, &App),
21975) {
21976    let window = cx.add_window(|window, cx| {
21977        let buffer = MultiBuffer::build_simple(text, cx);
21978        Editor::new(EditorMode::full(), buffer, None, window, cx)
21979    });
21980    let cx = &mut VisualTestContext::from_window(*window, cx);
21981
21982    let (buffer, snapshot) = window
21983        .update(cx, |editor, _window, cx| {
21984            (
21985                editor.buffer().clone(),
21986                editor.buffer().read(cx).snapshot(cx),
21987            )
21988        })
21989        .unwrap();
21990
21991    let edits = edits
21992        .into_iter()
21993        .map(|(range, edit)| {
21994            (
21995                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
21996                edit,
21997            )
21998        })
21999        .collect::<Vec<_>>();
22000
22001    let text_anchor_edits = edits
22002        .clone()
22003        .into_iter()
22004        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
22005        .collect::<Vec<_>>();
22006
22007    let edit_preview = window
22008        .update(cx, |_, _window, cx| {
22009            buffer
22010                .read(cx)
22011                .as_singleton()
22012                .unwrap()
22013                .read(cx)
22014                .preview_edits(text_anchor_edits.into(), cx)
22015        })
22016        .unwrap()
22017        .await;
22018
22019    cx.update(|_window, cx| {
22020        let highlighted_edits = edit_prediction_edit_text(
22021            snapshot.as_singleton().unwrap().2,
22022            &edits,
22023            &edit_preview,
22024            include_deletions,
22025            cx,
22026        );
22027        assertion_fn(highlighted_edits, cx)
22028    });
22029}
22030
22031#[track_caller]
22032fn assert_breakpoint(
22033    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
22034    path: &Arc<Path>,
22035    expected: Vec<(u32, Breakpoint)>,
22036) {
22037    if expected.is_empty() {
22038        assert!(!breakpoints.contains_key(path), "{}", path.display());
22039    } else {
22040        let mut breakpoint = breakpoints
22041            .get(path)
22042            .unwrap()
22043            .iter()
22044            .map(|breakpoint| {
22045                (
22046                    breakpoint.row,
22047                    Breakpoint {
22048                        message: breakpoint.message.clone(),
22049                        state: breakpoint.state,
22050                        condition: breakpoint.condition.clone(),
22051                        hit_condition: breakpoint.hit_condition.clone(),
22052                    },
22053                )
22054            })
22055            .collect::<Vec<_>>();
22056
22057        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
22058
22059        assert_eq!(expected, breakpoint);
22060    }
22061}
22062
22063fn add_log_breakpoint_at_cursor(
22064    editor: &mut Editor,
22065    log_message: &str,
22066    window: &mut Window,
22067    cx: &mut Context<Editor>,
22068) {
22069    let (anchor, bp) = editor
22070        .breakpoints_at_cursors(window, cx)
22071        .first()
22072        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
22073        .unwrap_or_else(|| {
22074            let cursor_position: Point = editor.selections.newest(cx).head();
22075
22076            let breakpoint_position = editor
22077                .snapshot(window, cx)
22078                .display_snapshot
22079                .buffer_snapshot
22080                .anchor_before(Point::new(cursor_position.row, 0));
22081
22082            (breakpoint_position, Breakpoint::new_log(log_message))
22083        });
22084
22085    editor.edit_breakpoint_at_anchor(
22086        anchor,
22087        bp,
22088        BreakpointEditAction::EditLogMessage(log_message.into()),
22089        cx,
22090    );
22091}
22092
22093#[gpui::test]
22094async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
22095    init_test(cx, |_| {});
22096
22097    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22098    let fs = FakeFs::new(cx.executor());
22099    fs.insert_tree(
22100        path!("/a"),
22101        json!({
22102            "main.rs": sample_text,
22103        }),
22104    )
22105    .await;
22106    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22107    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22108    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22109
22110    let fs = FakeFs::new(cx.executor());
22111    fs.insert_tree(
22112        path!("/a"),
22113        json!({
22114            "main.rs": sample_text,
22115        }),
22116    )
22117    .await;
22118    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22119    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22120    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22121    let worktree_id = workspace
22122        .update(cx, |workspace, _window, cx| {
22123            workspace.project().update(cx, |project, cx| {
22124                project.worktrees(cx).next().unwrap().read(cx).id()
22125            })
22126        })
22127        .unwrap();
22128
22129    let buffer = project
22130        .update(cx, |project, cx| {
22131            project.open_buffer((worktree_id, "main.rs"), cx)
22132        })
22133        .await
22134        .unwrap();
22135
22136    let (editor, cx) = cx.add_window_view(|window, cx| {
22137        Editor::new(
22138            EditorMode::full(),
22139            MultiBuffer::build_from_buffer(buffer, cx),
22140            Some(project.clone()),
22141            window,
22142            cx,
22143        )
22144    });
22145
22146    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22147    let abs_path = project.read_with(cx, |project, cx| {
22148        project
22149            .absolute_path(&project_path, cx)
22150            .map(Arc::from)
22151            .unwrap()
22152    });
22153
22154    // assert we can add breakpoint on the first line
22155    editor.update_in(cx, |editor, window, cx| {
22156        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22157        editor.move_to_end(&MoveToEnd, window, cx);
22158        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22159    });
22160
22161    let breakpoints = editor.update(cx, |editor, cx| {
22162        editor
22163            .breakpoint_store()
22164            .as_ref()
22165            .unwrap()
22166            .read(cx)
22167            .all_source_breakpoints(cx)
22168    });
22169
22170    assert_eq!(1, breakpoints.len());
22171    assert_breakpoint(
22172        &breakpoints,
22173        &abs_path,
22174        vec![
22175            (0, Breakpoint::new_standard()),
22176            (3, Breakpoint::new_standard()),
22177        ],
22178    );
22179
22180    editor.update_in(cx, |editor, window, cx| {
22181        editor.move_to_beginning(&MoveToBeginning, window, cx);
22182        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22183    });
22184
22185    let breakpoints = editor.update(cx, |editor, cx| {
22186        editor
22187            .breakpoint_store()
22188            .as_ref()
22189            .unwrap()
22190            .read(cx)
22191            .all_source_breakpoints(cx)
22192    });
22193
22194    assert_eq!(1, breakpoints.len());
22195    assert_breakpoint(
22196        &breakpoints,
22197        &abs_path,
22198        vec![(3, Breakpoint::new_standard())],
22199    );
22200
22201    editor.update_in(cx, |editor, window, cx| {
22202        editor.move_to_end(&MoveToEnd, window, cx);
22203        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22204    });
22205
22206    let breakpoints = editor.update(cx, |editor, cx| {
22207        editor
22208            .breakpoint_store()
22209            .as_ref()
22210            .unwrap()
22211            .read(cx)
22212            .all_source_breakpoints(cx)
22213    });
22214
22215    assert_eq!(0, breakpoints.len());
22216    assert_breakpoint(&breakpoints, &abs_path, vec![]);
22217}
22218
22219#[gpui::test]
22220async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
22221    init_test(cx, |_| {});
22222
22223    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22224
22225    let fs = FakeFs::new(cx.executor());
22226    fs.insert_tree(
22227        path!("/a"),
22228        json!({
22229            "main.rs": sample_text,
22230        }),
22231    )
22232    .await;
22233    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22234    let (workspace, cx) =
22235        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22236
22237    let worktree_id = workspace.update(cx, |workspace, cx| {
22238        workspace.project().update(cx, |project, cx| {
22239            project.worktrees(cx).next().unwrap().read(cx).id()
22240        })
22241    });
22242
22243    let buffer = project
22244        .update(cx, |project, cx| {
22245            project.open_buffer((worktree_id, "main.rs"), cx)
22246        })
22247        .await
22248        .unwrap();
22249
22250    let (editor, cx) = cx.add_window_view(|window, cx| {
22251        Editor::new(
22252            EditorMode::full(),
22253            MultiBuffer::build_from_buffer(buffer, cx),
22254            Some(project.clone()),
22255            window,
22256            cx,
22257        )
22258    });
22259
22260    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22261    let abs_path = project.read_with(cx, |project, cx| {
22262        project
22263            .absolute_path(&project_path, cx)
22264            .map(Arc::from)
22265            .unwrap()
22266    });
22267
22268    editor.update_in(cx, |editor, window, cx| {
22269        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
22270    });
22271
22272    let breakpoints = editor.update(cx, |editor, cx| {
22273        editor
22274            .breakpoint_store()
22275            .as_ref()
22276            .unwrap()
22277            .read(cx)
22278            .all_source_breakpoints(cx)
22279    });
22280
22281    assert_breakpoint(
22282        &breakpoints,
22283        &abs_path,
22284        vec![(0, Breakpoint::new_log("hello world"))],
22285    );
22286
22287    // Removing a log message from a log breakpoint should remove it
22288    editor.update_in(cx, |editor, window, cx| {
22289        add_log_breakpoint_at_cursor(editor, "", window, cx);
22290    });
22291
22292    let breakpoints = editor.update(cx, |editor, cx| {
22293        editor
22294            .breakpoint_store()
22295            .as_ref()
22296            .unwrap()
22297            .read(cx)
22298            .all_source_breakpoints(cx)
22299    });
22300
22301    assert_breakpoint(&breakpoints, &abs_path, vec![]);
22302
22303    editor.update_in(cx, |editor, window, cx| {
22304        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22305        editor.move_to_end(&MoveToEnd, window, cx);
22306        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22307        // Not adding a log message to a standard breakpoint shouldn't remove it
22308        add_log_breakpoint_at_cursor(editor, "", window, cx);
22309    });
22310
22311    let breakpoints = editor.update(cx, |editor, cx| {
22312        editor
22313            .breakpoint_store()
22314            .as_ref()
22315            .unwrap()
22316            .read(cx)
22317            .all_source_breakpoints(cx)
22318    });
22319
22320    assert_breakpoint(
22321        &breakpoints,
22322        &abs_path,
22323        vec![
22324            (0, Breakpoint::new_standard()),
22325            (3, Breakpoint::new_standard()),
22326        ],
22327    );
22328
22329    editor.update_in(cx, |editor, window, cx| {
22330        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
22331    });
22332
22333    let breakpoints = editor.update(cx, |editor, cx| {
22334        editor
22335            .breakpoint_store()
22336            .as_ref()
22337            .unwrap()
22338            .read(cx)
22339            .all_source_breakpoints(cx)
22340    });
22341
22342    assert_breakpoint(
22343        &breakpoints,
22344        &abs_path,
22345        vec![
22346            (0, Breakpoint::new_standard()),
22347            (3, Breakpoint::new_log("hello world")),
22348        ],
22349    );
22350
22351    editor.update_in(cx, |editor, window, cx| {
22352        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
22353    });
22354
22355    let breakpoints = editor.update(cx, |editor, cx| {
22356        editor
22357            .breakpoint_store()
22358            .as_ref()
22359            .unwrap()
22360            .read(cx)
22361            .all_source_breakpoints(cx)
22362    });
22363
22364    assert_breakpoint(
22365        &breakpoints,
22366        &abs_path,
22367        vec![
22368            (0, Breakpoint::new_standard()),
22369            (3, Breakpoint::new_log("hello Earth!!")),
22370        ],
22371    );
22372}
22373
22374/// This also tests that Editor::breakpoint_at_cursor_head is working properly
22375/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
22376/// or when breakpoints were placed out of order. This tests for a regression too
22377#[gpui::test]
22378async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
22379    init_test(cx, |_| {});
22380
22381    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22382    let fs = FakeFs::new(cx.executor());
22383    fs.insert_tree(
22384        path!("/a"),
22385        json!({
22386            "main.rs": sample_text,
22387        }),
22388    )
22389    .await;
22390    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22391    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22392    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22393
22394    let fs = FakeFs::new(cx.executor());
22395    fs.insert_tree(
22396        path!("/a"),
22397        json!({
22398            "main.rs": sample_text,
22399        }),
22400    )
22401    .await;
22402    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22403    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22404    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22405    let worktree_id = workspace
22406        .update(cx, |workspace, _window, cx| {
22407            workspace.project().update(cx, |project, cx| {
22408                project.worktrees(cx).next().unwrap().read(cx).id()
22409            })
22410        })
22411        .unwrap();
22412
22413    let buffer = project
22414        .update(cx, |project, cx| {
22415            project.open_buffer((worktree_id, "main.rs"), cx)
22416        })
22417        .await
22418        .unwrap();
22419
22420    let (editor, cx) = cx.add_window_view(|window, cx| {
22421        Editor::new(
22422            EditorMode::full(),
22423            MultiBuffer::build_from_buffer(buffer, cx),
22424            Some(project.clone()),
22425            window,
22426            cx,
22427        )
22428    });
22429
22430    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22431    let abs_path = project.read_with(cx, |project, cx| {
22432        project
22433            .absolute_path(&project_path, cx)
22434            .map(Arc::from)
22435            .unwrap()
22436    });
22437
22438    // assert we can add breakpoint on the first line
22439    editor.update_in(cx, |editor, window, cx| {
22440        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22441        editor.move_to_end(&MoveToEnd, window, cx);
22442        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22443        editor.move_up(&MoveUp, window, cx);
22444        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22445    });
22446
22447    let breakpoints = editor.update(cx, |editor, cx| {
22448        editor
22449            .breakpoint_store()
22450            .as_ref()
22451            .unwrap()
22452            .read(cx)
22453            .all_source_breakpoints(cx)
22454    });
22455
22456    assert_eq!(1, breakpoints.len());
22457    assert_breakpoint(
22458        &breakpoints,
22459        &abs_path,
22460        vec![
22461            (0, Breakpoint::new_standard()),
22462            (2, Breakpoint::new_standard()),
22463            (3, Breakpoint::new_standard()),
22464        ],
22465    );
22466
22467    editor.update_in(cx, |editor, window, cx| {
22468        editor.move_to_beginning(&MoveToBeginning, window, cx);
22469        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22470        editor.move_to_end(&MoveToEnd, window, cx);
22471        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22472        // Disabling a breakpoint that doesn't exist should do nothing
22473        editor.move_up(&MoveUp, window, cx);
22474        editor.move_up(&MoveUp, window, cx);
22475        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22476    });
22477
22478    let breakpoints = editor.update(cx, |editor, cx| {
22479        editor
22480            .breakpoint_store()
22481            .as_ref()
22482            .unwrap()
22483            .read(cx)
22484            .all_source_breakpoints(cx)
22485    });
22486
22487    let disable_breakpoint = {
22488        let mut bp = Breakpoint::new_standard();
22489        bp.state = BreakpointState::Disabled;
22490        bp
22491    };
22492
22493    assert_eq!(1, breakpoints.len());
22494    assert_breakpoint(
22495        &breakpoints,
22496        &abs_path,
22497        vec![
22498            (0, disable_breakpoint.clone()),
22499            (2, Breakpoint::new_standard()),
22500            (3, disable_breakpoint.clone()),
22501        ],
22502    );
22503
22504    editor.update_in(cx, |editor, window, cx| {
22505        editor.move_to_beginning(&MoveToBeginning, window, cx);
22506        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
22507        editor.move_to_end(&MoveToEnd, window, cx);
22508        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
22509        editor.move_up(&MoveUp, window, cx);
22510        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22511    });
22512
22513    let breakpoints = editor.update(cx, |editor, cx| {
22514        editor
22515            .breakpoint_store()
22516            .as_ref()
22517            .unwrap()
22518            .read(cx)
22519            .all_source_breakpoints(cx)
22520    });
22521
22522    assert_eq!(1, breakpoints.len());
22523    assert_breakpoint(
22524        &breakpoints,
22525        &abs_path,
22526        vec![
22527            (0, Breakpoint::new_standard()),
22528            (2, disable_breakpoint),
22529            (3, Breakpoint::new_standard()),
22530        ],
22531    );
22532}
22533
22534#[gpui::test]
22535async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
22536    init_test(cx, |_| {});
22537    let capabilities = lsp::ServerCapabilities {
22538        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
22539            prepare_provider: Some(true),
22540            work_done_progress_options: Default::default(),
22541        })),
22542        ..Default::default()
22543    };
22544    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
22545
22546    cx.set_state(indoc! {"
22547        struct Fˇoo {}
22548    "});
22549
22550    cx.update_editor(|editor, _, cx| {
22551        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
22552        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
22553        editor.highlight_background::<DocumentHighlightRead>(
22554            &[highlight_range],
22555            |theme| theme.colors().editor_document_highlight_read_background,
22556            cx,
22557        );
22558    });
22559
22560    let mut prepare_rename_handler = cx
22561        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
22562            move |_, _, _| async move {
22563                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
22564                    start: lsp::Position {
22565                        line: 0,
22566                        character: 7,
22567                    },
22568                    end: lsp::Position {
22569                        line: 0,
22570                        character: 10,
22571                    },
22572                })))
22573            },
22574        );
22575    let prepare_rename_task = cx
22576        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
22577        .expect("Prepare rename was not started");
22578    prepare_rename_handler.next().await.unwrap();
22579    prepare_rename_task.await.expect("Prepare rename failed");
22580
22581    let mut rename_handler =
22582        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
22583            let edit = lsp::TextEdit {
22584                range: lsp::Range {
22585                    start: lsp::Position {
22586                        line: 0,
22587                        character: 7,
22588                    },
22589                    end: lsp::Position {
22590                        line: 0,
22591                        character: 10,
22592                    },
22593                },
22594                new_text: "FooRenamed".to_string(),
22595            };
22596            Ok(Some(lsp::WorkspaceEdit::new(
22597                // Specify the same edit twice
22598                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
22599            )))
22600        });
22601    let rename_task = cx
22602        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
22603        .expect("Confirm rename was not started");
22604    rename_handler.next().await.unwrap();
22605    rename_task.await.expect("Confirm rename failed");
22606    cx.run_until_parked();
22607
22608    // Despite two edits, only one is actually applied as those are identical
22609    cx.assert_editor_state(indoc! {"
22610        struct FooRenamedˇ {}
22611    "});
22612}
22613
22614#[gpui::test]
22615async fn test_rename_without_prepare(cx: &mut TestAppContext) {
22616    init_test(cx, |_| {});
22617    // These capabilities indicate that the server does not support prepare rename.
22618    let capabilities = lsp::ServerCapabilities {
22619        rename_provider: Some(lsp::OneOf::Left(true)),
22620        ..Default::default()
22621    };
22622    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
22623
22624    cx.set_state(indoc! {"
22625        struct Fˇoo {}
22626    "});
22627
22628    cx.update_editor(|editor, _window, cx| {
22629        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
22630        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
22631        editor.highlight_background::<DocumentHighlightRead>(
22632            &[highlight_range],
22633            |theme| theme.colors().editor_document_highlight_read_background,
22634            cx,
22635        );
22636    });
22637
22638    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
22639        .expect("Prepare rename was not started")
22640        .await
22641        .expect("Prepare rename failed");
22642
22643    let mut rename_handler =
22644        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
22645            let edit = lsp::TextEdit {
22646                range: lsp::Range {
22647                    start: lsp::Position {
22648                        line: 0,
22649                        character: 7,
22650                    },
22651                    end: lsp::Position {
22652                        line: 0,
22653                        character: 10,
22654                    },
22655                },
22656                new_text: "FooRenamed".to_string(),
22657            };
22658            Ok(Some(lsp::WorkspaceEdit::new(
22659                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
22660            )))
22661        });
22662    let rename_task = cx
22663        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
22664        .expect("Confirm rename was not started");
22665    rename_handler.next().await.unwrap();
22666    rename_task.await.expect("Confirm rename failed");
22667    cx.run_until_parked();
22668
22669    // Correct range is renamed, as `surrounding_word` is used to find it.
22670    cx.assert_editor_state(indoc! {"
22671        struct FooRenamedˇ {}
22672    "});
22673}
22674
22675#[gpui::test]
22676async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
22677    init_test(cx, |_| {});
22678    let mut cx = EditorTestContext::new(cx).await;
22679
22680    let language = Arc::new(
22681        Language::new(
22682            LanguageConfig::default(),
22683            Some(tree_sitter_html::LANGUAGE.into()),
22684        )
22685        .with_brackets_query(
22686            r#"
22687            ("<" @open "/>" @close)
22688            ("</" @open ">" @close)
22689            ("<" @open ">" @close)
22690            ("\"" @open "\"" @close)
22691            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
22692        "#,
22693        )
22694        .unwrap(),
22695    );
22696    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22697
22698    cx.set_state(indoc! {"
22699        <span>ˇ</span>
22700    "});
22701    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
22702    cx.assert_editor_state(indoc! {"
22703        <span>
22704        ˇ
22705        </span>
22706    "});
22707
22708    cx.set_state(indoc! {"
22709        <span><span></span>ˇ</span>
22710    "});
22711    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
22712    cx.assert_editor_state(indoc! {"
22713        <span><span></span>
22714        ˇ</span>
22715    "});
22716
22717    cx.set_state(indoc! {"
22718        <span>ˇ
22719        </span>
22720    "});
22721    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
22722    cx.assert_editor_state(indoc! {"
22723        <span>
22724        ˇ
22725        </span>
22726    "});
22727}
22728
22729#[gpui::test(iterations = 10)]
22730async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
22731    init_test(cx, |_| {});
22732
22733    let fs = FakeFs::new(cx.executor());
22734    fs.insert_tree(
22735        path!("/dir"),
22736        json!({
22737            "a.ts": "a",
22738        }),
22739    )
22740    .await;
22741
22742    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
22743    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22744    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22745
22746    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22747    language_registry.add(Arc::new(Language::new(
22748        LanguageConfig {
22749            name: "TypeScript".into(),
22750            matcher: LanguageMatcher {
22751                path_suffixes: vec!["ts".to_string()],
22752                ..Default::default()
22753            },
22754            ..Default::default()
22755        },
22756        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
22757    )));
22758    let mut fake_language_servers = language_registry.register_fake_lsp(
22759        "TypeScript",
22760        FakeLspAdapter {
22761            capabilities: lsp::ServerCapabilities {
22762                code_lens_provider: Some(lsp::CodeLensOptions {
22763                    resolve_provider: Some(true),
22764                }),
22765                execute_command_provider: Some(lsp::ExecuteCommandOptions {
22766                    commands: vec!["_the/command".to_string()],
22767                    ..lsp::ExecuteCommandOptions::default()
22768                }),
22769                ..lsp::ServerCapabilities::default()
22770            },
22771            ..FakeLspAdapter::default()
22772        },
22773    );
22774
22775    let editor = workspace
22776        .update(cx, |workspace, window, cx| {
22777            workspace.open_abs_path(
22778                PathBuf::from(path!("/dir/a.ts")),
22779                OpenOptions::default(),
22780                window,
22781                cx,
22782            )
22783        })
22784        .unwrap()
22785        .await
22786        .unwrap()
22787        .downcast::<Editor>()
22788        .unwrap();
22789    cx.executor().run_until_parked();
22790
22791    let fake_server = fake_language_servers.next().await.unwrap();
22792
22793    let buffer = editor.update(cx, |editor, cx| {
22794        editor
22795            .buffer()
22796            .read(cx)
22797            .as_singleton()
22798            .expect("have opened a single file by path")
22799    });
22800
22801    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
22802    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
22803    drop(buffer_snapshot);
22804    let actions = cx
22805        .update_window(*workspace, |_, window, cx| {
22806            project.code_actions(&buffer, anchor..anchor, window, cx)
22807        })
22808        .unwrap();
22809
22810    fake_server
22811        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
22812            Ok(Some(vec![
22813                lsp::CodeLens {
22814                    range: lsp::Range::default(),
22815                    command: Some(lsp::Command {
22816                        title: "Code lens command".to_owned(),
22817                        command: "_the/command".to_owned(),
22818                        arguments: None,
22819                    }),
22820                    data: None,
22821                },
22822                lsp::CodeLens {
22823                    range: lsp::Range::default(),
22824                    command: Some(lsp::Command {
22825                        title: "Command not in capabilities".to_owned(),
22826                        command: "not in capabilities".to_owned(),
22827                        arguments: None,
22828                    }),
22829                    data: None,
22830                },
22831                lsp::CodeLens {
22832                    range: lsp::Range {
22833                        start: lsp::Position {
22834                            line: 1,
22835                            character: 1,
22836                        },
22837                        end: lsp::Position {
22838                            line: 1,
22839                            character: 1,
22840                        },
22841                    },
22842                    command: Some(lsp::Command {
22843                        title: "Command not in range".to_owned(),
22844                        command: "_the/command".to_owned(),
22845                        arguments: None,
22846                    }),
22847                    data: None,
22848                },
22849            ]))
22850        })
22851        .next()
22852        .await;
22853
22854    let actions = actions.await.unwrap();
22855    assert_eq!(
22856        actions.len(),
22857        1,
22858        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
22859    );
22860    let action = actions[0].clone();
22861    let apply = project.update(cx, |project, cx| {
22862        project.apply_code_action(buffer.clone(), action, true, cx)
22863    });
22864
22865    // Resolving the code action does not populate its edits. In absence of
22866    // edits, we must execute the given command.
22867    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
22868        |mut lens, _| async move {
22869            let lens_command = lens.command.as_mut().expect("should have a command");
22870            assert_eq!(lens_command.title, "Code lens command");
22871            lens_command.arguments = Some(vec![json!("the-argument")]);
22872            Ok(lens)
22873        },
22874    );
22875
22876    // While executing the command, the language server sends the editor
22877    // a `workspaceEdit` request.
22878    fake_server
22879        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
22880            let fake = fake_server.clone();
22881            move |params, _| {
22882                assert_eq!(params.command, "_the/command");
22883                let fake = fake.clone();
22884                async move {
22885                    fake.server
22886                        .request::<lsp::request::ApplyWorkspaceEdit>(
22887                            lsp::ApplyWorkspaceEditParams {
22888                                label: None,
22889                                edit: lsp::WorkspaceEdit {
22890                                    changes: Some(
22891                                        [(
22892                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
22893                                            vec![lsp::TextEdit {
22894                                                range: lsp::Range::new(
22895                                                    lsp::Position::new(0, 0),
22896                                                    lsp::Position::new(0, 0),
22897                                                ),
22898                                                new_text: "X".into(),
22899                                            }],
22900                                        )]
22901                                        .into_iter()
22902                                        .collect(),
22903                                    ),
22904                                    ..lsp::WorkspaceEdit::default()
22905                                },
22906                            },
22907                        )
22908                        .await
22909                        .into_response()
22910                        .unwrap();
22911                    Ok(Some(json!(null)))
22912                }
22913            }
22914        })
22915        .next()
22916        .await;
22917
22918    // Applying the code lens command returns a project transaction containing the edits
22919    // sent by the language server in its `workspaceEdit` request.
22920    let transaction = apply.await.unwrap();
22921    assert!(transaction.0.contains_key(&buffer));
22922    buffer.update(cx, |buffer, cx| {
22923        assert_eq!(buffer.text(), "Xa");
22924        buffer.undo(cx);
22925        assert_eq!(buffer.text(), "a");
22926    });
22927
22928    let actions_after_edits = cx
22929        .update_window(*workspace, |_, window, cx| {
22930            project.code_actions(&buffer, anchor..anchor, window, cx)
22931        })
22932        .unwrap()
22933        .await
22934        .unwrap();
22935    assert_eq!(
22936        actions, actions_after_edits,
22937        "For the same selection, same code lens actions should be returned"
22938    );
22939
22940    let _responses =
22941        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
22942            panic!("No more code lens requests are expected");
22943        });
22944    editor.update_in(cx, |editor, window, cx| {
22945        editor.select_all(&SelectAll, window, cx);
22946    });
22947    cx.executor().run_until_parked();
22948    let new_actions = cx
22949        .update_window(*workspace, |_, window, cx| {
22950            project.code_actions(&buffer, anchor..anchor, window, cx)
22951        })
22952        .unwrap()
22953        .await
22954        .unwrap();
22955    assert_eq!(
22956        actions, new_actions,
22957        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
22958    );
22959}
22960
22961#[gpui::test]
22962async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
22963    init_test(cx, |_| {});
22964
22965    let fs = FakeFs::new(cx.executor());
22966    let main_text = r#"fn main() {
22967println!("1");
22968println!("2");
22969println!("3");
22970println!("4");
22971println!("5");
22972}"#;
22973    let lib_text = "mod foo {}";
22974    fs.insert_tree(
22975        path!("/a"),
22976        json!({
22977            "lib.rs": lib_text,
22978            "main.rs": main_text,
22979        }),
22980    )
22981    .await;
22982
22983    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22984    let (workspace, cx) =
22985        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22986    let worktree_id = workspace.update(cx, |workspace, cx| {
22987        workspace.project().update(cx, |project, cx| {
22988            project.worktrees(cx).next().unwrap().read(cx).id()
22989        })
22990    });
22991
22992    let expected_ranges = vec![
22993        Point::new(0, 0)..Point::new(0, 0),
22994        Point::new(1, 0)..Point::new(1, 1),
22995        Point::new(2, 0)..Point::new(2, 2),
22996        Point::new(3, 0)..Point::new(3, 3),
22997    ];
22998
22999    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23000    let editor_1 = workspace
23001        .update_in(cx, |workspace, window, cx| {
23002            workspace.open_path(
23003                (worktree_id, "main.rs"),
23004                Some(pane_1.downgrade()),
23005                true,
23006                window,
23007                cx,
23008            )
23009        })
23010        .unwrap()
23011        .await
23012        .downcast::<Editor>()
23013        .unwrap();
23014    pane_1.update(cx, |pane, cx| {
23015        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23016        open_editor.update(cx, |editor, cx| {
23017            assert_eq!(
23018                editor.display_text(cx),
23019                main_text,
23020                "Original main.rs text on initial open",
23021            );
23022            assert_eq!(
23023                editor
23024                    .selections
23025                    .all::<Point>(cx)
23026                    .into_iter()
23027                    .map(|s| s.range())
23028                    .collect::<Vec<_>>(),
23029                vec![Point::zero()..Point::zero()],
23030                "Default selections on initial open",
23031            );
23032        })
23033    });
23034    editor_1.update_in(cx, |editor, window, cx| {
23035        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23036            s.select_ranges(expected_ranges.clone());
23037        });
23038    });
23039
23040    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
23041        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
23042    });
23043    let editor_2 = workspace
23044        .update_in(cx, |workspace, window, cx| {
23045            workspace.open_path(
23046                (worktree_id, "main.rs"),
23047                Some(pane_2.downgrade()),
23048                true,
23049                window,
23050                cx,
23051            )
23052        })
23053        .unwrap()
23054        .await
23055        .downcast::<Editor>()
23056        .unwrap();
23057    pane_2.update(cx, |pane, cx| {
23058        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23059        open_editor.update(cx, |editor, cx| {
23060            assert_eq!(
23061                editor.display_text(cx),
23062                main_text,
23063                "Original main.rs text on initial open in another panel",
23064            );
23065            assert_eq!(
23066                editor
23067                    .selections
23068                    .all::<Point>(cx)
23069                    .into_iter()
23070                    .map(|s| s.range())
23071                    .collect::<Vec<_>>(),
23072                vec![Point::zero()..Point::zero()],
23073                "Default selections on initial open in another panel",
23074            );
23075        })
23076    });
23077
23078    editor_2.update_in(cx, |editor, window, cx| {
23079        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
23080    });
23081
23082    let _other_editor_1 = workspace
23083        .update_in(cx, |workspace, window, cx| {
23084            workspace.open_path(
23085                (worktree_id, "lib.rs"),
23086                Some(pane_1.downgrade()),
23087                true,
23088                window,
23089                cx,
23090            )
23091        })
23092        .unwrap()
23093        .await
23094        .downcast::<Editor>()
23095        .unwrap();
23096    pane_1
23097        .update_in(cx, |pane, window, cx| {
23098            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23099        })
23100        .await
23101        .unwrap();
23102    drop(editor_1);
23103    pane_1.update(cx, |pane, cx| {
23104        pane.active_item()
23105            .unwrap()
23106            .downcast::<Editor>()
23107            .unwrap()
23108            .update(cx, |editor, cx| {
23109                assert_eq!(
23110                    editor.display_text(cx),
23111                    lib_text,
23112                    "Other file should be open and active",
23113                );
23114            });
23115        assert_eq!(pane.items().count(), 1, "No other editors should be open");
23116    });
23117
23118    let _other_editor_2 = workspace
23119        .update_in(cx, |workspace, window, cx| {
23120            workspace.open_path(
23121                (worktree_id, "lib.rs"),
23122                Some(pane_2.downgrade()),
23123                true,
23124                window,
23125                cx,
23126            )
23127        })
23128        .unwrap()
23129        .await
23130        .downcast::<Editor>()
23131        .unwrap();
23132    pane_2
23133        .update_in(cx, |pane, window, cx| {
23134            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23135        })
23136        .await
23137        .unwrap();
23138    drop(editor_2);
23139    pane_2.update(cx, |pane, cx| {
23140        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23141        open_editor.update(cx, |editor, cx| {
23142            assert_eq!(
23143                editor.display_text(cx),
23144                lib_text,
23145                "Other file should be open and active in another panel too",
23146            );
23147        });
23148        assert_eq!(
23149            pane.items().count(),
23150            1,
23151            "No other editors should be open in another pane",
23152        );
23153    });
23154
23155    let _editor_1_reopened = workspace
23156        .update_in(cx, |workspace, window, cx| {
23157            workspace.open_path(
23158                (worktree_id, "main.rs"),
23159                Some(pane_1.downgrade()),
23160                true,
23161                window,
23162                cx,
23163            )
23164        })
23165        .unwrap()
23166        .await
23167        .downcast::<Editor>()
23168        .unwrap();
23169    let _editor_2_reopened = workspace
23170        .update_in(cx, |workspace, window, cx| {
23171            workspace.open_path(
23172                (worktree_id, "main.rs"),
23173                Some(pane_2.downgrade()),
23174                true,
23175                window,
23176                cx,
23177            )
23178        })
23179        .unwrap()
23180        .await
23181        .downcast::<Editor>()
23182        .unwrap();
23183    pane_1.update(cx, |pane, cx| {
23184        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23185        open_editor.update(cx, |editor, cx| {
23186            assert_eq!(
23187                editor.display_text(cx),
23188                main_text,
23189                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
23190            );
23191            assert_eq!(
23192                editor
23193                    .selections
23194                    .all::<Point>(cx)
23195                    .into_iter()
23196                    .map(|s| s.range())
23197                    .collect::<Vec<_>>(),
23198                expected_ranges,
23199                "Previous editor in the 1st panel had selections and should get them restored on reopen",
23200            );
23201        })
23202    });
23203    pane_2.update(cx, |pane, cx| {
23204        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23205        open_editor.update(cx, |editor, cx| {
23206            assert_eq!(
23207                editor.display_text(cx),
23208                r#"fn main() {
23209⋯rintln!("1");
23210⋯intln!("2");
23211⋯ntln!("3");
23212println!("4");
23213println!("5");
23214}"#,
23215                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
23216            );
23217            assert_eq!(
23218                editor
23219                    .selections
23220                    .all::<Point>(cx)
23221                    .into_iter()
23222                    .map(|s| s.range())
23223                    .collect::<Vec<_>>(),
23224                vec![Point::zero()..Point::zero()],
23225                "Previous editor in the 2nd pane had no selections changed hence should restore none",
23226            );
23227        })
23228    });
23229}
23230
23231#[gpui::test]
23232async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
23233    init_test(cx, |_| {});
23234
23235    let fs = FakeFs::new(cx.executor());
23236    let main_text = r#"fn main() {
23237println!("1");
23238println!("2");
23239println!("3");
23240println!("4");
23241println!("5");
23242}"#;
23243    let lib_text = "mod foo {}";
23244    fs.insert_tree(
23245        path!("/a"),
23246        json!({
23247            "lib.rs": lib_text,
23248            "main.rs": main_text,
23249        }),
23250    )
23251    .await;
23252
23253    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23254    let (workspace, cx) =
23255        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23256    let worktree_id = workspace.update(cx, |workspace, cx| {
23257        workspace.project().update(cx, |project, cx| {
23258            project.worktrees(cx).next().unwrap().read(cx).id()
23259        })
23260    });
23261
23262    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23263    let editor = workspace
23264        .update_in(cx, |workspace, window, cx| {
23265            workspace.open_path(
23266                (worktree_id, "main.rs"),
23267                Some(pane.downgrade()),
23268                true,
23269                window,
23270                cx,
23271            )
23272        })
23273        .unwrap()
23274        .await
23275        .downcast::<Editor>()
23276        .unwrap();
23277    pane.update(cx, |pane, cx| {
23278        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23279        open_editor.update(cx, |editor, cx| {
23280            assert_eq!(
23281                editor.display_text(cx),
23282                main_text,
23283                "Original main.rs text on initial open",
23284            );
23285        })
23286    });
23287    editor.update_in(cx, |editor, window, cx| {
23288        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
23289    });
23290
23291    cx.update_global(|store: &mut SettingsStore, cx| {
23292        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
23293            s.restore_on_file_reopen = Some(false);
23294        });
23295    });
23296    editor.update_in(cx, |editor, window, cx| {
23297        editor.fold_ranges(
23298            vec![
23299                Point::new(1, 0)..Point::new(1, 1),
23300                Point::new(2, 0)..Point::new(2, 2),
23301                Point::new(3, 0)..Point::new(3, 3),
23302            ],
23303            false,
23304            window,
23305            cx,
23306        );
23307    });
23308    pane.update_in(cx, |pane, window, cx| {
23309        pane.close_all_items(&CloseAllItems::default(), window, cx)
23310    })
23311    .await
23312    .unwrap();
23313    pane.update(cx, |pane, _| {
23314        assert!(pane.active_item().is_none());
23315    });
23316    cx.update_global(|store: &mut SettingsStore, cx| {
23317        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
23318            s.restore_on_file_reopen = Some(true);
23319        });
23320    });
23321
23322    let _editor_reopened = workspace
23323        .update_in(cx, |workspace, window, cx| {
23324            workspace.open_path(
23325                (worktree_id, "main.rs"),
23326                Some(pane.downgrade()),
23327                true,
23328                window,
23329                cx,
23330            )
23331        })
23332        .unwrap()
23333        .await
23334        .downcast::<Editor>()
23335        .unwrap();
23336    pane.update(cx, |pane, cx| {
23337        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23338        open_editor.update(cx, |editor, cx| {
23339            assert_eq!(
23340                editor.display_text(cx),
23341                main_text,
23342                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
23343            );
23344        })
23345    });
23346}
23347
23348#[gpui::test]
23349async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
23350    struct EmptyModalView {
23351        focus_handle: gpui::FocusHandle,
23352    }
23353    impl EventEmitter<DismissEvent> for EmptyModalView {}
23354    impl Render for EmptyModalView {
23355        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
23356            div()
23357        }
23358    }
23359    impl Focusable for EmptyModalView {
23360        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
23361            self.focus_handle.clone()
23362        }
23363    }
23364    impl workspace::ModalView for EmptyModalView {}
23365    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
23366        EmptyModalView {
23367            focus_handle: cx.focus_handle(),
23368        }
23369    }
23370
23371    init_test(cx, |_| {});
23372
23373    let fs = FakeFs::new(cx.executor());
23374    let project = Project::test(fs, [], cx).await;
23375    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23376    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
23377    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23378    let editor = cx.new_window_entity(|window, cx| {
23379        Editor::new(
23380            EditorMode::full(),
23381            buffer,
23382            Some(project.clone()),
23383            window,
23384            cx,
23385        )
23386    });
23387    workspace
23388        .update(cx, |workspace, window, cx| {
23389            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
23390        })
23391        .unwrap();
23392    editor.update_in(cx, |editor, window, cx| {
23393        editor.open_context_menu(&OpenContextMenu, window, cx);
23394        assert!(editor.mouse_context_menu.is_some());
23395    });
23396    workspace
23397        .update(cx, |workspace, window, cx| {
23398            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
23399        })
23400        .unwrap();
23401    cx.read(|cx| {
23402        assert!(editor.read(cx).mouse_context_menu.is_none());
23403    });
23404}
23405
23406#[gpui::test]
23407async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
23408    init_test(cx, |_| {});
23409
23410    let fs = FakeFs::new(cx.executor());
23411    fs.insert_file(path!("/file.html"), Default::default())
23412        .await;
23413
23414    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
23415
23416    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23417    let html_language = Arc::new(Language::new(
23418        LanguageConfig {
23419            name: "HTML".into(),
23420            matcher: LanguageMatcher {
23421                path_suffixes: vec!["html".to_string()],
23422                ..LanguageMatcher::default()
23423            },
23424            brackets: BracketPairConfig {
23425                pairs: vec![BracketPair {
23426                    start: "<".into(),
23427                    end: ">".into(),
23428                    close: true,
23429                    ..Default::default()
23430                }],
23431                ..Default::default()
23432            },
23433            ..Default::default()
23434        },
23435        Some(tree_sitter_html::LANGUAGE.into()),
23436    ));
23437    language_registry.add(html_language);
23438    let mut fake_servers = language_registry.register_fake_lsp(
23439        "HTML",
23440        FakeLspAdapter {
23441            capabilities: lsp::ServerCapabilities {
23442                completion_provider: Some(lsp::CompletionOptions {
23443                    resolve_provider: Some(true),
23444                    ..Default::default()
23445                }),
23446                ..Default::default()
23447            },
23448            ..Default::default()
23449        },
23450    );
23451
23452    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23453    let cx = &mut VisualTestContext::from_window(*workspace, cx);
23454
23455    let worktree_id = workspace
23456        .update(cx, |workspace, _window, cx| {
23457            workspace.project().update(cx, |project, cx| {
23458                project.worktrees(cx).next().unwrap().read(cx).id()
23459            })
23460        })
23461        .unwrap();
23462    project
23463        .update(cx, |project, cx| {
23464            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
23465        })
23466        .await
23467        .unwrap();
23468    let editor = workspace
23469        .update(cx, |workspace, window, cx| {
23470            workspace.open_path((worktree_id, "file.html"), None, true, window, cx)
23471        })
23472        .unwrap()
23473        .await
23474        .unwrap()
23475        .downcast::<Editor>()
23476        .unwrap();
23477
23478    let fake_server = fake_servers.next().await.unwrap();
23479    editor.update_in(cx, |editor, window, cx| {
23480        editor.set_text("<ad></ad>", window, cx);
23481        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23482            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
23483        });
23484        let Some((buffer, _)) = editor
23485            .buffer
23486            .read(cx)
23487            .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
23488        else {
23489            panic!("Failed to get buffer for selection position");
23490        };
23491        let buffer = buffer.read(cx);
23492        let buffer_id = buffer.remote_id();
23493        let opening_range =
23494            buffer.anchor_before(Point::new(0, 1))..buffer.anchor_after(Point::new(0, 3));
23495        let closing_range =
23496            buffer.anchor_before(Point::new(0, 6))..buffer.anchor_after(Point::new(0, 8));
23497        let mut linked_ranges = HashMap::default();
23498        linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
23499        editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
23500    });
23501    let mut completion_handle =
23502        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
23503            Ok(Some(lsp::CompletionResponse::Array(vec![
23504                lsp::CompletionItem {
23505                    label: "head".to_string(),
23506                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23507                        lsp::InsertReplaceEdit {
23508                            new_text: "head".to_string(),
23509                            insert: lsp::Range::new(
23510                                lsp::Position::new(0, 1),
23511                                lsp::Position::new(0, 3),
23512                            ),
23513                            replace: lsp::Range::new(
23514                                lsp::Position::new(0, 1),
23515                                lsp::Position::new(0, 3),
23516                            ),
23517                        },
23518                    )),
23519                    ..Default::default()
23520                },
23521            ])))
23522        });
23523    editor.update_in(cx, |editor, window, cx| {
23524        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
23525    });
23526    cx.run_until_parked();
23527    completion_handle.next().await.unwrap();
23528    editor.update(cx, |editor, _| {
23529        assert!(
23530            editor.context_menu_visible(),
23531            "Completion menu should be visible"
23532        );
23533    });
23534    editor.update_in(cx, |editor, window, cx| {
23535        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
23536    });
23537    cx.executor().run_until_parked();
23538    editor.update(cx, |editor, cx| {
23539        assert_eq!(editor.text(cx), "<head></head>");
23540    });
23541}
23542
23543#[gpui::test]
23544async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
23545    init_test(cx, |_| {});
23546
23547    let fs = FakeFs::new(cx.executor());
23548    fs.insert_tree(
23549        path!("/root"),
23550        json!({
23551            "a": {
23552                "main.rs": "fn main() {}",
23553            },
23554            "foo": {
23555                "bar": {
23556                    "external_file.rs": "pub mod external {}",
23557                }
23558            }
23559        }),
23560    )
23561    .await;
23562
23563    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
23564    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23565    language_registry.add(rust_lang());
23566    let _fake_servers = language_registry.register_fake_lsp(
23567        "Rust",
23568        FakeLspAdapter {
23569            ..FakeLspAdapter::default()
23570        },
23571    );
23572    let (workspace, cx) =
23573        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23574    let worktree_id = workspace.update(cx, |workspace, cx| {
23575        workspace.project().update(cx, |project, cx| {
23576            project.worktrees(cx).next().unwrap().read(cx).id()
23577        })
23578    });
23579
23580    let assert_language_servers_count =
23581        |expected: usize, context: &str, cx: &mut VisualTestContext| {
23582            project.update(cx, |project, cx| {
23583                let current = project
23584                    .lsp_store()
23585                    .read(cx)
23586                    .as_local()
23587                    .unwrap()
23588                    .language_servers
23589                    .len();
23590                assert_eq!(expected, current, "{context}");
23591            });
23592        };
23593
23594    assert_language_servers_count(
23595        0,
23596        "No servers should be running before any file is open",
23597        cx,
23598    );
23599    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23600    let main_editor = workspace
23601        .update_in(cx, |workspace, window, cx| {
23602            workspace.open_path(
23603                (worktree_id, "main.rs"),
23604                Some(pane.downgrade()),
23605                true,
23606                window,
23607                cx,
23608            )
23609        })
23610        .unwrap()
23611        .await
23612        .downcast::<Editor>()
23613        .unwrap();
23614    pane.update(cx, |pane, cx| {
23615        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23616        open_editor.update(cx, |editor, cx| {
23617            assert_eq!(
23618                editor.display_text(cx),
23619                "fn main() {}",
23620                "Original main.rs text on initial open",
23621            );
23622        });
23623        assert_eq!(open_editor, main_editor);
23624    });
23625    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
23626
23627    let external_editor = workspace
23628        .update_in(cx, |workspace, window, cx| {
23629            workspace.open_abs_path(
23630                PathBuf::from("/root/foo/bar/external_file.rs"),
23631                OpenOptions::default(),
23632                window,
23633                cx,
23634            )
23635        })
23636        .await
23637        .expect("opening external file")
23638        .downcast::<Editor>()
23639        .expect("downcasted external file's open element to editor");
23640    pane.update(cx, |pane, cx| {
23641        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23642        open_editor.update(cx, |editor, cx| {
23643            assert_eq!(
23644                editor.display_text(cx),
23645                "pub mod external {}",
23646                "External file is open now",
23647            );
23648        });
23649        assert_eq!(open_editor, external_editor);
23650    });
23651    assert_language_servers_count(
23652        1,
23653        "Second, external, *.rs file should join the existing server",
23654        cx,
23655    );
23656
23657    pane.update_in(cx, |pane, window, cx| {
23658        pane.close_active_item(&CloseActiveItem::default(), window, cx)
23659    })
23660    .await
23661    .unwrap();
23662    pane.update_in(cx, |pane, window, cx| {
23663        pane.navigate_backward(&Default::default(), window, cx);
23664    });
23665    cx.run_until_parked();
23666    pane.update(cx, |pane, cx| {
23667        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23668        open_editor.update(cx, |editor, cx| {
23669            assert_eq!(
23670                editor.display_text(cx),
23671                "pub mod external {}",
23672                "External file is open now",
23673            );
23674        });
23675    });
23676    assert_language_servers_count(
23677        1,
23678        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
23679        cx,
23680    );
23681
23682    cx.update(|_, cx| {
23683        workspace::reload(cx);
23684    });
23685    assert_language_servers_count(
23686        1,
23687        "After reloading the worktree with local and external files opened, only one project should be started",
23688        cx,
23689    );
23690}
23691
23692#[gpui::test]
23693async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
23694    init_test(cx, |_| {});
23695
23696    let mut cx = EditorTestContext::new(cx).await;
23697    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
23698    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23699
23700    // test cursor move to start of each line on tab
23701    // for `if`, `elif`, `else`, `while`, `with` and `for`
23702    cx.set_state(indoc! {"
23703        def main():
23704        ˇ    for item in items:
23705        ˇ        while item.active:
23706        ˇ            if item.value > 10:
23707        ˇ                continue
23708        ˇ            elif item.value < 0:
23709        ˇ                break
23710        ˇ            else:
23711        ˇ                with item.context() as ctx:
23712        ˇ                    yield count
23713        ˇ        else:
23714        ˇ            log('while else')
23715        ˇ    else:
23716        ˇ        log('for else')
23717    "});
23718    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
23719    cx.assert_editor_state(indoc! {"
23720        def main():
23721            ˇfor item in items:
23722                ˇwhile item.active:
23723                    ˇif item.value > 10:
23724                        ˇcontinue
23725                    ˇelif item.value < 0:
23726                        ˇbreak
23727                    ˇelse:
23728                        ˇwith item.context() as ctx:
23729                            ˇyield count
23730                ˇelse:
23731                    ˇlog('while else')
23732            ˇelse:
23733                ˇlog('for else')
23734    "});
23735    // test relative indent is preserved when tab
23736    // for `if`, `elif`, `else`, `while`, `with` and `for`
23737    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
23738    cx.assert_editor_state(indoc! {"
23739        def main():
23740                ˇfor item in items:
23741                    ˇwhile item.active:
23742                        ˇif item.value > 10:
23743                            ˇcontinue
23744                        ˇelif item.value < 0:
23745                            ˇbreak
23746                        ˇelse:
23747                            ˇwith item.context() as ctx:
23748                                ˇyield count
23749                    ˇelse:
23750                        ˇlog('while else')
23751                ˇelse:
23752                    ˇlog('for else')
23753    "});
23754
23755    // test cursor move to start of each line on tab
23756    // for `try`, `except`, `else`, `finally`, `match` and `def`
23757    cx.set_state(indoc! {"
23758        def main():
23759        ˇ    try:
23760        ˇ        fetch()
23761        ˇ    except ValueError:
23762        ˇ        handle_error()
23763        ˇ    else:
23764        ˇ        match value:
23765        ˇ            case _:
23766        ˇ    finally:
23767        ˇ        def status():
23768        ˇ            return 0
23769    "});
23770    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
23771    cx.assert_editor_state(indoc! {"
23772        def main():
23773            ˇtry:
23774                ˇfetch()
23775            ˇexcept ValueError:
23776                ˇhandle_error()
23777            ˇelse:
23778                ˇmatch value:
23779                    ˇcase _:
23780            ˇfinally:
23781                ˇdef status():
23782                    ˇreturn 0
23783    "});
23784    // test relative indent is preserved when tab
23785    // for `try`, `except`, `else`, `finally`, `match` and `def`
23786    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
23787    cx.assert_editor_state(indoc! {"
23788        def main():
23789                ˇtry:
23790                    ˇfetch()
23791                ˇexcept ValueError:
23792                    ˇhandle_error()
23793                ˇelse:
23794                    ˇmatch value:
23795                        ˇcase _:
23796                ˇfinally:
23797                    ˇdef status():
23798                        ˇreturn 0
23799    "});
23800}
23801
23802#[gpui::test]
23803async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
23804    init_test(cx, |_| {});
23805
23806    let mut cx = EditorTestContext::new(cx).await;
23807    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
23808    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23809
23810    // test `else` auto outdents when typed inside `if` block
23811    cx.set_state(indoc! {"
23812        def main():
23813            if i == 2:
23814                return
23815                ˇ
23816    "});
23817    cx.update_editor(|editor, window, cx| {
23818        editor.handle_input("else:", window, cx);
23819    });
23820    cx.assert_editor_state(indoc! {"
23821        def main():
23822            if i == 2:
23823                return
23824            else:ˇ
23825    "});
23826
23827    // test `except` auto outdents when typed inside `try` block
23828    cx.set_state(indoc! {"
23829        def main():
23830            try:
23831                i = 2
23832                ˇ
23833    "});
23834    cx.update_editor(|editor, window, cx| {
23835        editor.handle_input("except:", window, cx);
23836    });
23837    cx.assert_editor_state(indoc! {"
23838        def main():
23839            try:
23840                i = 2
23841            except:ˇ
23842    "});
23843
23844    // test `else` auto outdents when typed inside `except` block
23845    cx.set_state(indoc! {"
23846        def main():
23847            try:
23848                i = 2
23849            except:
23850                j = 2
23851                ˇ
23852    "});
23853    cx.update_editor(|editor, window, cx| {
23854        editor.handle_input("else:", window, cx);
23855    });
23856    cx.assert_editor_state(indoc! {"
23857        def main():
23858            try:
23859                i = 2
23860            except:
23861                j = 2
23862            else:ˇ
23863    "});
23864
23865    // test `finally` auto outdents when typed inside `else` block
23866    cx.set_state(indoc! {"
23867        def main():
23868            try:
23869                i = 2
23870            except:
23871                j = 2
23872            else:
23873                k = 2
23874                ˇ
23875    "});
23876    cx.update_editor(|editor, window, cx| {
23877        editor.handle_input("finally:", window, cx);
23878    });
23879    cx.assert_editor_state(indoc! {"
23880        def main():
23881            try:
23882                i = 2
23883            except:
23884                j = 2
23885            else:
23886                k = 2
23887            finally:ˇ
23888    "});
23889
23890    // test `else` does not outdents when typed inside `except` block right after for block
23891    cx.set_state(indoc! {"
23892        def main():
23893            try:
23894                i = 2
23895            except:
23896                for i in range(n):
23897                    pass
23898                ˇ
23899    "});
23900    cx.update_editor(|editor, window, cx| {
23901        editor.handle_input("else:", window, cx);
23902    });
23903    cx.assert_editor_state(indoc! {"
23904        def main():
23905            try:
23906                i = 2
23907            except:
23908                for i in range(n):
23909                    pass
23910                else:ˇ
23911    "});
23912
23913    // test `finally` auto outdents when typed inside `else` block right after for block
23914    cx.set_state(indoc! {"
23915        def main():
23916            try:
23917                i = 2
23918            except:
23919                j = 2
23920            else:
23921                for i in range(n):
23922                    pass
23923                ˇ
23924    "});
23925    cx.update_editor(|editor, window, cx| {
23926        editor.handle_input("finally:", window, cx);
23927    });
23928    cx.assert_editor_state(indoc! {"
23929        def main():
23930            try:
23931                i = 2
23932            except:
23933                j = 2
23934            else:
23935                for i in range(n):
23936                    pass
23937            finally:ˇ
23938    "});
23939
23940    // test `except` outdents to inner "try" block
23941    cx.set_state(indoc! {"
23942        def main():
23943            try:
23944                i = 2
23945                if i == 2:
23946                    try:
23947                        i = 3
23948                        ˇ
23949    "});
23950    cx.update_editor(|editor, window, cx| {
23951        editor.handle_input("except:", window, cx);
23952    });
23953    cx.assert_editor_state(indoc! {"
23954        def main():
23955            try:
23956                i = 2
23957                if i == 2:
23958                    try:
23959                        i = 3
23960                    except:ˇ
23961    "});
23962
23963    // test `except` outdents to outer "try" block
23964    cx.set_state(indoc! {"
23965        def main():
23966            try:
23967                i = 2
23968                if i == 2:
23969                    try:
23970                        i = 3
23971                ˇ
23972    "});
23973    cx.update_editor(|editor, window, cx| {
23974        editor.handle_input("except:", window, cx);
23975    });
23976    cx.assert_editor_state(indoc! {"
23977        def main():
23978            try:
23979                i = 2
23980                if i == 2:
23981                    try:
23982                        i = 3
23983            except:ˇ
23984    "});
23985
23986    // test `else` stays at correct indent when typed after `for` block
23987    cx.set_state(indoc! {"
23988        def main():
23989            for i in range(10):
23990                if i == 3:
23991                    break
23992            ˇ
23993    "});
23994    cx.update_editor(|editor, window, cx| {
23995        editor.handle_input("else:", window, cx);
23996    });
23997    cx.assert_editor_state(indoc! {"
23998        def main():
23999            for i in range(10):
24000                if i == 3:
24001                    break
24002            else:ˇ
24003    "});
24004
24005    // test does not outdent on typing after line with square brackets
24006    cx.set_state(indoc! {"
24007        def f() -> list[str]:
24008            ˇ
24009    "});
24010    cx.update_editor(|editor, window, cx| {
24011        editor.handle_input("a", window, cx);
24012    });
24013    cx.assert_editor_state(indoc! {"
24014        def f() -> list[str]:
2401524016    "});
24017
24018    // test does not outdent on typing : after case keyword
24019    cx.set_state(indoc! {"
24020        match 1:
24021            caseˇ
24022    "});
24023    cx.update_editor(|editor, window, cx| {
24024        editor.handle_input(":", window, cx);
24025    });
24026    cx.assert_editor_state(indoc! {"
24027        match 1:
24028            case:ˇ
24029    "});
24030}
24031
24032#[gpui::test]
24033async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
24034    init_test(cx, |_| {});
24035    update_test_language_settings(cx, |settings| {
24036        settings.defaults.extend_comment_on_newline = Some(false);
24037    });
24038    let mut cx = EditorTestContext::new(cx).await;
24039    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24040    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24041
24042    // test correct indent after newline on comment
24043    cx.set_state(indoc! {"
24044        # COMMENT:ˇ
24045    "});
24046    cx.update_editor(|editor, window, cx| {
24047        editor.newline(&Newline, window, cx);
24048    });
24049    cx.assert_editor_state(indoc! {"
24050        # COMMENT:
24051        ˇ
24052    "});
24053
24054    // test correct indent after newline in brackets
24055    cx.set_state(indoc! {"
24056        {ˇ}
24057    "});
24058    cx.update_editor(|editor, window, cx| {
24059        editor.newline(&Newline, window, cx);
24060    });
24061    cx.run_until_parked();
24062    cx.assert_editor_state(indoc! {"
24063        {
24064            ˇ
24065        }
24066    "});
24067
24068    cx.set_state(indoc! {"
24069        (ˇ)
24070    "});
24071    cx.update_editor(|editor, window, cx| {
24072        editor.newline(&Newline, window, cx);
24073    });
24074    cx.run_until_parked();
24075    cx.assert_editor_state(indoc! {"
24076        (
24077            ˇ
24078        )
24079    "});
24080
24081    // do not indent after empty lists or dictionaries
24082    cx.set_state(indoc! {"
24083        a = []ˇ
24084    "});
24085    cx.update_editor(|editor, window, cx| {
24086        editor.newline(&Newline, window, cx);
24087    });
24088    cx.run_until_parked();
24089    cx.assert_editor_state(indoc! {"
24090        a = []
24091        ˇ
24092    "});
24093}
24094
24095#[gpui::test]
24096async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
24097    init_test(cx, |_| {});
24098
24099    let mut cx = EditorTestContext::new(cx).await;
24100    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24101    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24102
24103    // test cursor move to start of each line on tab
24104    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
24105    cx.set_state(indoc! {"
24106        function main() {
24107        ˇ    for item in $items; do
24108        ˇ        while [ -n \"$item\" ]; do
24109        ˇ            if [ \"$value\" -gt 10 ]; then
24110        ˇ                continue
24111        ˇ            elif [ \"$value\" -lt 0 ]; then
24112        ˇ                break
24113        ˇ            else
24114        ˇ                echo \"$item\"
24115        ˇ            fi
24116        ˇ        done
24117        ˇ    done
24118        ˇ}
24119    "});
24120    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24121    cx.assert_editor_state(indoc! {"
24122        function main() {
24123            ˇfor item in $items; do
24124                ˇwhile [ -n \"$item\" ]; do
24125                    ˇif [ \"$value\" -gt 10 ]; then
24126                        ˇcontinue
24127                    ˇelif [ \"$value\" -lt 0 ]; then
24128                        ˇbreak
24129                    ˇelse
24130                        ˇecho \"$item\"
24131                    ˇfi
24132                ˇdone
24133            ˇdone
24134        ˇ}
24135    "});
24136    // test relative indent is preserved when tab
24137    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24138    cx.assert_editor_state(indoc! {"
24139        function main() {
24140                ˇfor item in $items; do
24141                    ˇwhile [ -n \"$item\" ]; do
24142                        ˇif [ \"$value\" -gt 10 ]; then
24143                            ˇcontinue
24144                        ˇelif [ \"$value\" -lt 0 ]; then
24145                            ˇbreak
24146                        ˇelse
24147                            ˇecho \"$item\"
24148                        ˇfi
24149                    ˇdone
24150                ˇdone
24151            ˇ}
24152    "});
24153
24154    // test cursor move to start of each line on tab
24155    // for `case` statement with patterns
24156    cx.set_state(indoc! {"
24157        function handle() {
24158        ˇ    case \"$1\" in
24159        ˇ        start)
24160        ˇ            echo \"a\"
24161        ˇ            ;;
24162        ˇ        stop)
24163        ˇ            echo \"b\"
24164        ˇ            ;;
24165        ˇ        *)
24166        ˇ            echo \"c\"
24167        ˇ            ;;
24168        ˇ    esac
24169        ˇ}
24170    "});
24171    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24172    cx.assert_editor_state(indoc! {"
24173        function handle() {
24174            ˇcase \"$1\" in
24175                ˇstart)
24176                    ˇecho \"a\"
24177                    ˇ;;
24178                ˇstop)
24179                    ˇecho \"b\"
24180                    ˇ;;
24181                ˇ*)
24182                    ˇecho \"c\"
24183                    ˇ;;
24184            ˇesac
24185        ˇ}
24186    "});
24187}
24188
24189#[gpui::test]
24190async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
24191    init_test(cx, |_| {});
24192
24193    let mut cx = EditorTestContext::new(cx).await;
24194    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24195    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24196
24197    // test indents on comment insert
24198    cx.set_state(indoc! {"
24199        function main() {
24200        ˇ    for item in $items; do
24201        ˇ        while [ -n \"$item\" ]; do
24202        ˇ            if [ \"$value\" -gt 10 ]; then
24203        ˇ                continue
24204        ˇ            elif [ \"$value\" -lt 0 ]; then
24205        ˇ                break
24206        ˇ            else
24207        ˇ                echo \"$item\"
24208        ˇ            fi
24209        ˇ        done
24210        ˇ    done
24211        ˇ}
24212    "});
24213    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
24214    cx.assert_editor_state(indoc! {"
24215        function main() {
24216        #ˇ    for item in $items; do
24217        #ˇ        while [ -n \"$item\" ]; do
24218        #ˇ            if [ \"$value\" -gt 10 ]; then
24219        #ˇ                continue
24220        #ˇ            elif [ \"$value\" -lt 0 ]; then
24221        #ˇ                break
24222        #ˇ            else
24223        #ˇ                echo \"$item\"
24224        #ˇ            fi
24225        #ˇ        done
24226        #ˇ    done
24227        #ˇ}
24228    "});
24229}
24230
24231#[gpui::test]
24232async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
24233    init_test(cx, |_| {});
24234
24235    let mut cx = EditorTestContext::new(cx).await;
24236    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24237    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24238
24239    // test `else` auto outdents when typed inside `if` block
24240    cx.set_state(indoc! {"
24241        if [ \"$1\" = \"test\" ]; then
24242            echo \"foo bar\"
24243            ˇ
24244    "});
24245    cx.update_editor(|editor, window, cx| {
24246        editor.handle_input("else", window, cx);
24247    });
24248    cx.assert_editor_state(indoc! {"
24249        if [ \"$1\" = \"test\" ]; then
24250            echo \"foo bar\"
24251        elseˇ
24252    "});
24253
24254    // test `elif` auto outdents when typed inside `if` block
24255    cx.set_state(indoc! {"
24256        if [ \"$1\" = \"test\" ]; then
24257            echo \"foo bar\"
24258            ˇ
24259    "});
24260    cx.update_editor(|editor, window, cx| {
24261        editor.handle_input("elif", window, cx);
24262    });
24263    cx.assert_editor_state(indoc! {"
24264        if [ \"$1\" = \"test\" ]; then
24265            echo \"foo bar\"
24266        elifˇ
24267    "});
24268
24269    // test `fi` auto outdents when typed inside `else` block
24270    cx.set_state(indoc! {"
24271        if [ \"$1\" = \"test\" ]; then
24272            echo \"foo bar\"
24273        else
24274            echo \"bar baz\"
24275            ˇ
24276    "});
24277    cx.update_editor(|editor, window, cx| {
24278        editor.handle_input("fi", window, cx);
24279    });
24280    cx.assert_editor_state(indoc! {"
24281        if [ \"$1\" = \"test\" ]; then
24282            echo \"foo bar\"
24283        else
24284            echo \"bar baz\"
24285        fiˇ
24286    "});
24287
24288    // test `done` auto outdents when typed inside `while` block
24289    cx.set_state(indoc! {"
24290        while read line; do
24291            echo \"$line\"
24292            ˇ
24293    "});
24294    cx.update_editor(|editor, window, cx| {
24295        editor.handle_input("done", window, cx);
24296    });
24297    cx.assert_editor_state(indoc! {"
24298        while read line; do
24299            echo \"$line\"
24300        doneˇ
24301    "});
24302
24303    // test `done` auto outdents when typed inside `for` block
24304    cx.set_state(indoc! {"
24305        for file in *.txt; do
24306            cat \"$file\"
24307            ˇ
24308    "});
24309    cx.update_editor(|editor, window, cx| {
24310        editor.handle_input("done", window, cx);
24311    });
24312    cx.assert_editor_state(indoc! {"
24313        for file in *.txt; do
24314            cat \"$file\"
24315        doneˇ
24316    "});
24317
24318    // test `esac` auto outdents when typed inside `case` block
24319    cx.set_state(indoc! {"
24320        case \"$1\" in
24321            start)
24322                echo \"foo bar\"
24323                ;;
24324            stop)
24325                echo \"bar baz\"
24326                ;;
24327            ˇ
24328    "});
24329    cx.update_editor(|editor, window, cx| {
24330        editor.handle_input("esac", window, cx);
24331    });
24332    cx.assert_editor_state(indoc! {"
24333        case \"$1\" in
24334            start)
24335                echo \"foo bar\"
24336                ;;
24337            stop)
24338                echo \"bar baz\"
24339                ;;
24340        esacˇ
24341    "});
24342
24343    // test `*)` auto outdents when typed inside `case` block
24344    cx.set_state(indoc! {"
24345        case \"$1\" in
24346            start)
24347                echo \"foo bar\"
24348                ;;
24349                ˇ
24350    "});
24351    cx.update_editor(|editor, window, cx| {
24352        editor.handle_input("*)", window, cx);
24353    });
24354    cx.assert_editor_state(indoc! {"
24355        case \"$1\" in
24356            start)
24357                echo \"foo bar\"
24358                ;;
24359            *)ˇ
24360    "});
24361
24362    // test `fi` outdents to correct level with nested if blocks
24363    cx.set_state(indoc! {"
24364        if [ \"$1\" = \"test\" ]; then
24365            echo \"outer if\"
24366            if [ \"$2\" = \"debug\" ]; then
24367                echo \"inner if\"
24368                ˇ
24369    "});
24370    cx.update_editor(|editor, window, cx| {
24371        editor.handle_input("fi", window, cx);
24372    });
24373    cx.assert_editor_state(indoc! {"
24374        if [ \"$1\" = \"test\" ]; then
24375            echo \"outer if\"
24376            if [ \"$2\" = \"debug\" ]; then
24377                echo \"inner if\"
24378            fiˇ
24379    "});
24380}
24381
24382#[gpui::test]
24383async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
24384    init_test(cx, |_| {});
24385    update_test_language_settings(cx, |settings| {
24386        settings.defaults.extend_comment_on_newline = Some(false);
24387    });
24388    let mut cx = EditorTestContext::new(cx).await;
24389    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24390    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24391
24392    // test correct indent after newline on comment
24393    cx.set_state(indoc! {"
24394        # COMMENT:ˇ
24395    "});
24396    cx.update_editor(|editor, window, cx| {
24397        editor.newline(&Newline, window, cx);
24398    });
24399    cx.assert_editor_state(indoc! {"
24400        # COMMENT:
24401        ˇ
24402    "});
24403
24404    // test correct indent after newline after `then`
24405    cx.set_state(indoc! {"
24406
24407        if [ \"$1\" = \"test\" ]; thenˇ
24408    "});
24409    cx.update_editor(|editor, window, cx| {
24410        editor.newline(&Newline, window, cx);
24411    });
24412    cx.run_until_parked();
24413    cx.assert_editor_state(indoc! {"
24414
24415        if [ \"$1\" = \"test\" ]; then
24416            ˇ
24417    "});
24418
24419    // test correct indent after newline after `else`
24420    cx.set_state(indoc! {"
24421        if [ \"$1\" = \"test\" ]; then
24422        elseˇ
24423    "});
24424    cx.update_editor(|editor, window, cx| {
24425        editor.newline(&Newline, window, cx);
24426    });
24427    cx.run_until_parked();
24428    cx.assert_editor_state(indoc! {"
24429        if [ \"$1\" = \"test\" ]; then
24430        else
24431            ˇ
24432    "});
24433
24434    // test correct indent after newline after `elif`
24435    cx.set_state(indoc! {"
24436        if [ \"$1\" = \"test\" ]; then
24437        elifˇ
24438    "});
24439    cx.update_editor(|editor, window, cx| {
24440        editor.newline(&Newline, window, cx);
24441    });
24442    cx.run_until_parked();
24443    cx.assert_editor_state(indoc! {"
24444        if [ \"$1\" = \"test\" ]; then
24445        elif
24446            ˇ
24447    "});
24448
24449    // test correct indent after newline after `do`
24450    cx.set_state(indoc! {"
24451        for file in *.txt; doˇ
24452    "});
24453    cx.update_editor(|editor, window, cx| {
24454        editor.newline(&Newline, window, cx);
24455    });
24456    cx.run_until_parked();
24457    cx.assert_editor_state(indoc! {"
24458        for file in *.txt; do
24459            ˇ
24460    "});
24461
24462    // test correct indent after newline after case pattern
24463    cx.set_state(indoc! {"
24464        case \"$1\" in
24465            start)ˇ
24466    "});
24467    cx.update_editor(|editor, window, cx| {
24468        editor.newline(&Newline, window, cx);
24469    });
24470    cx.run_until_parked();
24471    cx.assert_editor_state(indoc! {"
24472        case \"$1\" in
24473            start)
24474                ˇ
24475    "});
24476
24477    // test correct indent after newline after case pattern
24478    cx.set_state(indoc! {"
24479        case \"$1\" in
24480            start)
24481                ;;
24482            *)ˇ
24483    "});
24484    cx.update_editor(|editor, window, cx| {
24485        editor.newline(&Newline, window, cx);
24486    });
24487    cx.run_until_parked();
24488    cx.assert_editor_state(indoc! {"
24489        case \"$1\" in
24490            start)
24491                ;;
24492            *)
24493                ˇ
24494    "});
24495
24496    // test correct indent after newline after function opening brace
24497    cx.set_state(indoc! {"
24498        function test() {ˇ}
24499    "});
24500    cx.update_editor(|editor, window, cx| {
24501        editor.newline(&Newline, window, cx);
24502    });
24503    cx.run_until_parked();
24504    cx.assert_editor_state(indoc! {"
24505        function test() {
24506            ˇ
24507        }
24508    "});
24509
24510    // test no extra indent after semicolon on same line
24511    cx.set_state(indoc! {"
24512        echo \"test\"24513    "});
24514    cx.update_editor(|editor, window, cx| {
24515        editor.newline(&Newline, window, cx);
24516    });
24517    cx.run_until_parked();
24518    cx.assert_editor_state(indoc! {"
24519        echo \"test\";
24520        ˇ
24521    "});
24522}
24523
24524fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
24525    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
24526    point..point
24527}
24528
24529#[track_caller]
24530fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
24531    let (text, ranges) = marked_text_ranges(marked_text, true);
24532    assert_eq!(editor.text(cx), text);
24533    assert_eq!(
24534        editor.selections.ranges(cx),
24535        ranges,
24536        "Assert selections are {}",
24537        marked_text
24538    );
24539}
24540
24541pub fn handle_signature_help_request(
24542    cx: &mut EditorLspTestContext,
24543    mocked_response: lsp::SignatureHelp,
24544) -> impl Future<Output = ()> + use<> {
24545    let mut request =
24546        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
24547            let mocked_response = mocked_response.clone();
24548            async move { Ok(Some(mocked_response)) }
24549        });
24550
24551    async move {
24552        request.next().await;
24553    }
24554}
24555
24556#[track_caller]
24557pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
24558    cx.update_editor(|editor, _, _| {
24559        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
24560            let entries = menu.entries.borrow();
24561            let entries = entries
24562                .iter()
24563                .map(|entry| entry.string.as_str())
24564                .collect::<Vec<_>>();
24565            assert_eq!(entries, expected);
24566        } else {
24567            panic!("Expected completions menu");
24568        }
24569    });
24570}
24571
24572/// Handle completion request passing a marked string specifying where the completion
24573/// should be triggered from using '|' character, what range should be replaced, and what completions
24574/// should be returned using '<' and '>' to delimit the range.
24575///
24576/// Also see `handle_completion_request_with_insert_and_replace`.
24577#[track_caller]
24578pub fn handle_completion_request(
24579    marked_string: &str,
24580    completions: Vec<&'static str>,
24581    is_incomplete: bool,
24582    counter: Arc<AtomicUsize>,
24583    cx: &mut EditorLspTestContext,
24584) -> impl Future<Output = ()> {
24585    let complete_from_marker: TextRangeMarker = '|'.into();
24586    let replace_range_marker: TextRangeMarker = ('<', '>').into();
24587    let (_, mut marked_ranges) = marked_text_ranges_by(
24588        marked_string,
24589        vec![complete_from_marker.clone(), replace_range_marker.clone()],
24590    );
24591
24592    let complete_from_position =
24593        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
24594    let replace_range =
24595        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
24596
24597    let mut request =
24598        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
24599            let completions = completions.clone();
24600            counter.fetch_add(1, atomic::Ordering::Release);
24601            async move {
24602                assert_eq!(params.text_document_position.text_document.uri, url.clone());
24603                assert_eq!(
24604                    params.text_document_position.position,
24605                    complete_from_position
24606                );
24607                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
24608                    is_incomplete,
24609                    item_defaults: None,
24610                    items: completions
24611                        .iter()
24612                        .map(|completion_text| lsp::CompletionItem {
24613                            label: completion_text.to_string(),
24614                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
24615                                range: replace_range,
24616                                new_text: completion_text.to_string(),
24617                            })),
24618                            ..Default::default()
24619                        })
24620                        .collect(),
24621                })))
24622            }
24623        });
24624
24625    async move {
24626        request.next().await;
24627    }
24628}
24629
24630/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
24631/// given instead, which also contains an `insert` range.
24632///
24633/// This function uses markers to define ranges:
24634/// - `|` marks the cursor position
24635/// - `<>` marks the replace range
24636/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
24637pub fn handle_completion_request_with_insert_and_replace(
24638    cx: &mut EditorLspTestContext,
24639    marked_string: &str,
24640    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
24641    counter: Arc<AtomicUsize>,
24642) -> impl Future<Output = ()> {
24643    let complete_from_marker: TextRangeMarker = '|'.into();
24644    let replace_range_marker: TextRangeMarker = ('<', '>').into();
24645    let insert_range_marker: TextRangeMarker = ('{', '}').into();
24646
24647    let (_, mut marked_ranges) = marked_text_ranges_by(
24648        marked_string,
24649        vec![
24650            complete_from_marker.clone(),
24651            replace_range_marker.clone(),
24652            insert_range_marker.clone(),
24653        ],
24654    );
24655
24656    let complete_from_position =
24657        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
24658    let replace_range =
24659        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
24660
24661    let insert_range = match marked_ranges.remove(&insert_range_marker) {
24662        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
24663        _ => lsp::Range {
24664            start: replace_range.start,
24665            end: complete_from_position,
24666        },
24667    };
24668
24669    let mut request =
24670        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
24671            let completions = completions.clone();
24672            counter.fetch_add(1, atomic::Ordering::Release);
24673            async move {
24674                assert_eq!(params.text_document_position.text_document.uri, url.clone());
24675                assert_eq!(
24676                    params.text_document_position.position, complete_from_position,
24677                    "marker `|` position doesn't match",
24678                );
24679                Ok(Some(lsp::CompletionResponse::Array(
24680                    completions
24681                        .iter()
24682                        .map(|(label, new_text)| lsp::CompletionItem {
24683                            label: label.to_string(),
24684                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
24685                                lsp::InsertReplaceEdit {
24686                                    insert: insert_range,
24687                                    replace: replace_range,
24688                                    new_text: new_text.to_string(),
24689                                },
24690                            )),
24691                            ..Default::default()
24692                        })
24693                        .collect(),
24694                )))
24695            }
24696        });
24697
24698    async move {
24699        request.next().await;
24700    }
24701}
24702
24703fn handle_resolve_completion_request(
24704    cx: &mut EditorLspTestContext,
24705    edits: Option<Vec<(&'static str, &'static str)>>,
24706) -> impl Future<Output = ()> {
24707    let edits = edits.map(|edits| {
24708        edits
24709            .iter()
24710            .map(|(marked_string, new_text)| {
24711                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
24712                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
24713                lsp::TextEdit::new(replace_range, new_text.to_string())
24714            })
24715            .collect::<Vec<_>>()
24716    });
24717
24718    let mut request =
24719        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
24720            let edits = edits.clone();
24721            async move {
24722                Ok(lsp::CompletionItem {
24723                    additional_text_edits: edits,
24724                    ..Default::default()
24725                })
24726            }
24727        });
24728
24729    async move {
24730        request.next().await;
24731    }
24732}
24733
24734pub(crate) fn update_test_language_settings(
24735    cx: &mut TestAppContext,
24736    f: impl Fn(&mut AllLanguageSettingsContent),
24737) {
24738    cx.update(|cx| {
24739        SettingsStore::update_global(cx, |store, cx| {
24740            store.update_user_settings::<AllLanguageSettings>(cx, f);
24741        });
24742    });
24743}
24744
24745pub(crate) fn update_test_project_settings(
24746    cx: &mut TestAppContext,
24747    f: impl Fn(&mut ProjectSettings),
24748) {
24749    cx.update(|cx| {
24750        SettingsStore::update_global(cx, |store, cx| {
24751            store.update_user_settings::<ProjectSettings>(cx, f);
24752        });
24753    });
24754}
24755
24756pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
24757    cx.update(|cx| {
24758        assets::Assets.load_test_fonts(cx);
24759        let store = SettingsStore::test(cx);
24760        cx.set_global(store);
24761        theme::init(theme::LoadThemes::JustBase, cx);
24762        release_channel::init(SemanticVersion::default(), cx);
24763        client::init_settings(cx);
24764        language::init(cx);
24765        Project::init_settings(cx);
24766        workspace::init_settings(cx);
24767        crate::init(cx);
24768    });
24769    zlog::init_test();
24770    update_test_language_settings(cx, f);
24771}
24772
24773#[track_caller]
24774fn assert_hunk_revert(
24775    not_reverted_text_with_selections: &str,
24776    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
24777    expected_reverted_text_with_selections: &str,
24778    base_text: &str,
24779    cx: &mut EditorLspTestContext,
24780) {
24781    cx.set_state(not_reverted_text_with_selections);
24782    cx.set_head_text(base_text);
24783    cx.executor().run_until_parked();
24784
24785    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
24786        let snapshot = editor.snapshot(window, cx);
24787        let reverted_hunk_statuses = snapshot
24788            .buffer_snapshot
24789            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
24790            .map(|hunk| hunk.status().kind)
24791            .collect::<Vec<_>>();
24792
24793        editor.git_restore(&Default::default(), window, cx);
24794        reverted_hunk_statuses
24795    });
24796    cx.executor().run_until_parked();
24797    cx.assert_editor_state(expected_reverted_text_with_selections);
24798    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
24799}
24800
24801#[gpui::test(iterations = 10)]
24802async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
24803    init_test(cx, |_| {});
24804
24805    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
24806    let counter = diagnostic_requests.clone();
24807
24808    let fs = FakeFs::new(cx.executor());
24809    fs.insert_tree(
24810        path!("/a"),
24811        json!({
24812            "first.rs": "fn main() { let a = 5; }",
24813            "second.rs": "// Test file",
24814        }),
24815    )
24816    .await;
24817
24818    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24819    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24820    let cx = &mut VisualTestContext::from_window(*workspace, cx);
24821
24822    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24823    language_registry.add(rust_lang());
24824    let mut fake_servers = language_registry.register_fake_lsp(
24825        "Rust",
24826        FakeLspAdapter {
24827            capabilities: lsp::ServerCapabilities {
24828                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
24829                    lsp::DiagnosticOptions {
24830                        identifier: None,
24831                        inter_file_dependencies: true,
24832                        workspace_diagnostics: true,
24833                        work_done_progress_options: Default::default(),
24834                    },
24835                )),
24836                ..Default::default()
24837            },
24838            ..Default::default()
24839        },
24840    );
24841
24842    let editor = workspace
24843        .update(cx, |workspace, window, cx| {
24844            workspace.open_abs_path(
24845                PathBuf::from(path!("/a/first.rs")),
24846                OpenOptions::default(),
24847                window,
24848                cx,
24849            )
24850        })
24851        .unwrap()
24852        .await
24853        .unwrap()
24854        .downcast::<Editor>()
24855        .unwrap();
24856    let fake_server = fake_servers.next().await.unwrap();
24857    let server_id = fake_server.server.server_id();
24858    let mut first_request = fake_server
24859        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
24860            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
24861            let result_id = Some(new_result_id.to_string());
24862            assert_eq!(
24863                params.text_document.uri,
24864                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
24865            );
24866            async move {
24867                Ok(lsp::DocumentDiagnosticReportResult::Report(
24868                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
24869                        related_documents: None,
24870                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
24871                            items: Vec::new(),
24872                            result_id,
24873                        },
24874                    }),
24875                ))
24876            }
24877        });
24878
24879    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
24880        project.update(cx, |project, cx| {
24881            let buffer_id = editor
24882                .read(cx)
24883                .buffer()
24884                .read(cx)
24885                .as_singleton()
24886                .expect("created a singleton buffer")
24887                .read(cx)
24888                .remote_id();
24889            let buffer_result_id = project
24890                .lsp_store()
24891                .read(cx)
24892                .result_id(server_id, buffer_id, cx);
24893            assert_eq!(expected, buffer_result_id);
24894        });
24895    };
24896
24897    ensure_result_id(None, cx);
24898    cx.executor().advance_clock(Duration::from_millis(60));
24899    cx.executor().run_until_parked();
24900    assert_eq!(
24901        diagnostic_requests.load(atomic::Ordering::Acquire),
24902        1,
24903        "Opening file should trigger diagnostic request"
24904    );
24905    first_request
24906        .next()
24907        .await
24908        .expect("should have sent the first diagnostics pull request");
24909    ensure_result_id(Some("1".to_string()), cx);
24910
24911    // Editing should trigger diagnostics
24912    editor.update_in(cx, |editor, window, cx| {
24913        editor.handle_input("2", window, cx)
24914    });
24915    cx.executor().advance_clock(Duration::from_millis(60));
24916    cx.executor().run_until_parked();
24917    assert_eq!(
24918        diagnostic_requests.load(atomic::Ordering::Acquire),
24919        2,
24920        "Editing should trigger diagnostic request"
24921    );
24922    ensure_result_id(Some("2".to_string()), cx);
24923
24924    // Moving cursor should not trigger diagnostic request
24925    editor.update_in(cx, |editor, window, cx| {
24926        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24927            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
24928        });
24929    });
24930    cx.executor().advance_clock(Duration::from_millis(60));
24931    cx.executor().run_until_parked();
24932    assert_eq!(
24933        diagnostic_requests.load(atomic::Ordering::Acquire),
24934        2,
24935        "Cursor movement should not trigger diagnostic request"
24936    );
24937    ensure_result_id(Some("2".to_string()), cx);
24938    // Multiple rapid edits should be debounced
24939    for _ in 0..5 {
24940        editor.update_in(cx, |editor, window, cx| {
24941            editor.handle_input("x", window, cx)
24942        });
24943    }
24944    cx.executor().advance_clock(Duration::from_millis(60));
24945    cx.executor().run_until_parked();
24946
24947    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
24948    assert!(
24949        final_requests <= 4,
24950        "Multiple rapid edits should be debounced (got {final_requests} requests)",
24951    );
24952    ensure_result_id(Some(final_requests.to_string()), cx);
24953}
24954
24955#[gpui::test]
24956async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
24957    // Regression test for issue #11671
24958    // Previously, adding a cursor after moving multiple cursors would reset
24959    // the cursor count instead of adding to the existing cursors.
24960    init_test(cx, |_| {});
24961    let mut cx = EditorTestContext::new(cx).await;
24962
24963    // Create a simple buffer with cursor at start
24964    cx.set_state(indoc! {"
24965        ˇaaaa
24966        bbbb
24967        cccc
24968        dddd
24969        eeee
24970        ffff
24971        gggg
24972        hhhh"});
24973
24974    // Add 2 cursors below (so we have 3 total)
24975    cx.update_editor(|editor, window, cx| {
24976        editor.add_selection_below(&Default::default(), window, cx);
24977        editor.add_selection_below(&Default::default(), window, cx);
24978    });
24979
24980    // Verify we have 3 cursors
24981    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
24982    assert_eq!(
24983        initial_count, 3,
24984        "Should have 3 cursors after adding 2 below"
24985    );
24986
24987    // Move down one line
24988    cx.update_editor(|editor, window, cx| {
24989        editor.move_down(&MoveDown, window, cx);
24990    });
24991
24992    // Add another cursor below
24993    cx.update_editor(|editor, window, cx| {
24994        editor.add_selection_below(&Default::default(), window, cx);
24995    });
24996
24997    // Should now have 4 cursors (3 original + 1 new)
24998    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
24999    assert_eq!(
25000        final_count, 4,
25001        "Should have 4 cursors after moving and adding another"
25002    );
25003}
25004
25005#[gpui::test(iterations = 10)]
25006async fn test_document_colors(cx: &mut TestAppContext) {
25007    let expected_color = Rgba {
25008        r: 0.33,
25009        g: 0.33,
25010        b: 0.33,
25011        a: 0.33,
25012    };
25013
25014    init_test(cx, |_| {});
25015
25016    let fs = FakeFs::new(cx.executor());
25017    fs.insert_tree(
25018        path!("/a"),
25019        json!({
25020            "first.rs": "fn main() { let a = 5; }",
25021        }),
25022    )
25023    .await;
25024
25025    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25026    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25027    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25028
25029    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25030    language_registry.add(rust_lang());
25031    let mut fake_servers = language_registry.register_fake_lsp(
25032        "Rust",
25033        FakeLspAdapter {
25034            capabilities: lsp::ServerCapabilities {
25035                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
25036                ..lsp::ServerCapabilities::default()
25037            },
25038            name: "rust-analyzer",
25039            ..FakeLspAdapter::default()
25040        },
25041    );
25042    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
25043        "Rust",
25044        FakeLspAdapter {
25045            capabilities: lsp::ServerCapabilities {
25046                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
25047                ..lsp::ServerCapabilities::default()
25048            },
25049            name: "not-rust-analyzer",
25050            ..FakeLspAdapter::default()
25051        },
25052    );
25053
25054    let editor = workspace
25055        .update(cx, |workspace, window, cx| {
25056            workspace.open_abs_path(
25057                PathBuf::from(path!("/a/first.rs")),
25058                OpenOptions::default(),
25059                window,
25060                cx,
25061            )
25062        })
25063        .unwrap()
25064        .await
25065        .unwrap()
25066        .downcast::<Editor>()
25067        .unwrap();
25068    let fake_language_server = fake_servers.next().await.unwrap();
25069    let fake_language_server_without_capabilities =
25070        fake_servers_without_capabilities.next().await.unwrap();
25071    let requests_made = Arc::new(AtomicUsize::new(0));
25072    let closure_requests_made = Arc::clone(&requests_made);
25073    let mut color_request_handle = fake_language_server
25074        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
25075            let requests_made = Arc::clone(&closure_requests_made);
25076            async move {
25077                assert_eq!(
25078                    params.text_document.uri,
25079                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25080                );
25081                requests_made.fetch_add(1, atomic::Ordering::Release);
25082                Ok(vec![
25083                    lsp::ColorInformation {
25084                        range: lsp::Range {
25085                            start: lsp::Position {
25086                                line: 0,
25087                                character: 0,
25088                            },
25089                            end: lsp::Position {
25090                                line: 0,
25091                                character: 1,
25092                            },
25093                        },
25094                        color: lsp::Color {
25095                            red: 0.33,
25096                            green: 0.33,
25097                            blue: 0.33,
25098                            alpha: 0.33,
25099                        },
25100                    },
25101                    lsp::ColorInformation {
25102                        range: lsp::Range {
25103                            start: lsp::Position {
25104                                line: 0,
25105                                character: 0,
25106                            },
25107                            end: lsp::Position {
25108                                line: 0,
25109                                character: 1,
25110                            },
25111                        },
25112                        color: lsp::Color {
25113                            red: 0.33,
25114                            green: 0.33,
25115                            blue: 0.33,
25116                            alpha: 0.33,
25117                        },
25118                    },
25119                ])
25120            }
25121        });
25122
25123    let _handle = fake_language_server_without_capabilities
25124        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
25125            panic!("Should not be called");
25126        });
25127    cx.executor().advance_clock(Duration::from_millis(100));
25128    color_request_handle.next().await.unwrap();
25129    cx.run_until_parked();
25130    assert_eq!(
25131        1,
25132        requests_made.load(atomic::Ordering::Acquire),
25133        "Should query for colors once per editor open"
25134    );
25135    editor.update_in(cx, |editor, _, cx| {
25136        assert_eq!(
25137            vec![expected_color],
25138            extract_color_inlays(editor, cx),
25139            "Should have an initial inlay"
25140        );
25141    });
25142
25143    // opening another file in a split should not influence the LSP query counter
25144    workspace
25145        .update(cx, |workspace, window, cx| {
25146            assert_eq!(
25147                workspace.panes().len(),
25148                1,
25149                "Should have one pane with one editor"
25150            );
25151            workspace.move_item_to_pane_in_direction(
25152                &MoveItemToPaneInDirection {
25153                    direction: SplitDirection::Right,
25154                    focus: false,
25155                    clone: true,
25156                },
25157                window,
25158                cx,
25159            );
25160        })
25161        .unwrap();
25162    cx.run_until_parked();
25163    workspace
25164        .update(cx, |workspace, _, cx| {
25165            let panes = workspace.panes();
25166            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
25167            for pane in panes {
25168                let editor = pane
25169                    .read(cx)
25170                    .active_item()
25171                    .and_then(|item| item.downcast::<Editor>())
25172                    .expect("Should have opened an editor in each split");
25173                let editor_file = editor
25174                    .read(cx)
25175                    .buffer()
25176                    .read(cx)
25177                    .as_singleton()
25178                    .expect("test deals with singleton buffers")
25179                    .read(cx)
25180                    .file()
25181                    .expect("test buffese should have a file")
25182                    .path();
25183                assert_eq!(
25184                    editor_file.as_ref(),
25185                    Path::new("first.rs"),
25186                    "Both editors should be opened for the same file"
25187                )
25188            }
25189        })
25190        .unwrap();
25191
25192    cx.executor().advance_clock(Duration::from_millis(500));
25193    let save = editor.update_in(cx, |editor, window, cx| {
25194        editor.move_to_end(&MoveToEnd, window, cx);
25195        editor.handle_input("dirty", window, cx);
25196        editor.save(
25197            SaveOptions {
25198                format: true,
25199                autosave: true,
25200            },
25201            project.clone(),
25202            window,
25203            cx,
25204        )
25205    });
25206    save.await.unwrap();
25207
25208    color_request_handle.next().await.unwrap();
25209    cx.run_until_parked();
25210    assert_eq!(
25211        3,
25212        requests_made.load(atomic::Ordering::Acquire),
25213        "Should query for colors once per save and once per formatting after save"
25214    );
25215
25216    drop(editor);
25217    let close = workspace
25218        .update(cx, |workspace, window, cx| {
25219            workspace.active_pane().update(cx, |pane, cx| {
25220                pane.close_active_item(&CloseActiveItem::default(), window, cx)
25221            })
25222        })
25223        .unwrap();
25224    close.await.unwrap();
25225    let close = workspace
25226        .update(cx, |workspace, window, cx| {
25227            workspace.active_pane().update(cx, |pane, cx| {
25228                pane.close_active_item(&CloseActiveItem::default(), window, cx)
25229            })
25230        })
25231        .unwrap();
25232    close.await.unwrap();
25233    assert_eq!(
25234        3,
25235        requests_made.load(atomic::Ordering::Acquire),
25236        "After saving and closing all editors, no extra requests should be made"
25237    );
25238    workspace
25239        .update(cx, |workspace, _, cx| {
25240            assert!(
25241                workspace.active_item(cx).is_none(),
25242                "Should close all editors"
25243            )
25244        })
25245        .unwrap();
25246
25247    workspace
25248        .update(cx, |workspace, window, cx| {
25249            workspace.active_pane().update(cx, |pane, cx| {
25250                pane.navigate_backward(&Default::default(), window, cx);
25251            })
25252        })
25253        .unwrap();
25254    cx.executor().advance_clock(Duration::from_millis(100));
25255    cx.run_until_parked();
25256    let editor = workspace
25257        .update(cx, |workspace, _, cx| {
25258            workspace
25259                .active_item(cx)
25260                .expect("Should have reopened the editor again after navigating back")
25261                .downcast::<Editor>()
25262                .expect("Should be an editor")
25263        })
25264        .unwrap();
25265    color_request_handle.next().await.unwrap();
25266    assert_eq!(
25267        3,
25268        requests_made.load(atomic::Ordering::Acquire),
25269        "Cache should be reused on buffer close and reopen"
25270    );
25271    editor.update(cx, |editor, cx| {
25272        assert_eq!(
25273            vec![expected_color],
25274            extract_color_inlays(editor, cx),
25275            "Should have an initial inlay"
25276        );
25277    });
25278}
25279
25280#[gpui::test]
25281async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
25282    init_test(cx, |_| {});
25283    let (editor, cx) = cx.add_window_view(Editor::single_line);
25284    editor.update_in(cx, |editor, window, cx| {
25285        editor.set_text("oops\n\nwow\n", window, cx)
25286    });
25287    cx.run_until_parked();
25288    editor.update(cx, |editor, cx| {
25289        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
25290    });
25291    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
25292    cx.run_until_parked();
25293    editor.update(cx, |editor, cx| {
25294        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
25295    });
25296}
25297
25298#[gpui::test]
25299async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
25300    init_test(cx, |_| {});
25301
25302    cx.update(|cx| {
25303        register_project_item::<Editor>(cx);
25304    });
25305
25306    let fs = FakeFs::new(cx.executor());
25307    fs.insert_tree("/root1", json!({})).await;
25308    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
25309        .await;
25310
25311    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
25312    let (workspace, cx) =
25313        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
25314
25315    let worktree_id = project.update(cx, |project, cx| {
25316        project.worktrees(cx).next().unwrap().read(cx).id()
25317    });
25318
25319    let handle = workspace
25320        .update_in(cx, |workspace, window, cx| {
25321            let project_path = (worktree_id, "one.pdf");
25322            workspace.open_path(project_path, None, true, window, cx)
25323        })
25324        .await
25325        .unwrap();
25326
25327    assert_eq!(
25328        handle.to_any().entity_type(),
25329        TypeId::of::<InvalidBufferView>()
25330    );
25331}
25332
25333#[gpui::test]
25334async fn test_select_next_prev_syntax_node(cx: &mut TestAppContext) {
25335    init_test(cx, |_| {});
25336
25337    let language = Arc::new(Language::new(
25338        LanguageConfig::default(),
25339        Some(tree_sitter_rust::LANGUAGE.into()),
25340    ));
25341
25342    // Test hierarchical sibling navigation
25343    let text = r#"
25344        fn outer() {
25345            if condition {
25346                let a = 1;
25347            }
25348            let b = 2;
25349        }
25350
25351        fn another() {
25352            let c = 3;
25353        }
25354    "#;
25355
25356    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
25357    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
25358    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
25359
25360    // Wait for parsing to complete
25361    editor
25362        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
25363        .await;
25364
25365    editor.update_in(cx, |editor, window, cx| {
25366        // Start by selecting "let a = 1;" inside the if block
25367        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25368            s.select_display_ranges([
25369                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 26)
25370            ]);
25371        });
25372
25373        let initial_selection = editor.selections.display_ranges(cx);
25374        assert_eq!(initial_selection.len(), 1, "Should have one selection");
25375
25376        // Test select next sibling - should move up levels to find the next sibling
25377        // Since "let a = 1;" has no siblings in the if block, it should move up
25378        // to find "let b = 2;" which is a sibling of the if block
25379        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
25380        let next_selection = editor.selections.display_ranges(cx);
25381
25382        // Should have a selection and it should be different from the initial
25383        assert_eq!(
25384            next_selection.len(),
25385            1,
25386            "Should have one selection after next"
25387        );
25388        assert_ne!(
25389            next_selection[0], initial_selection[0],
25390            "Next sibling selection should be different"
25391        );
25392
25393        // Test hierarchical navigation by going to the end of the current function
25394        // and trying to navigate to the next function
25395        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25396            s.select_display_ranges([
25397                DisplayPoint::new(DisplayRow(5), 12)..DisplayPoint::new(DisplayRow(5), 22)
25398            ]);
25399        });
25400
25401        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
25402        let function_next_selection = editor.selections.display_ranges(cx);
25403
25404        // Should move to the next function
25405        assert_eq!(
25406            function_next_selection.len(),
25407            1,
25408            "Should have one selection after function next"
25409        );
25410
25411        // Test select previous sibling navigation
25412        editor.select_prev_syntax_node(&SelectPreviousSyntaxNode, window, cx);
25413        let prev_selection = editor.selections.display_ranges(cx);
25414
25415        // Should have a selection and it should be different
25416        assert_eq!(
25417            prev_selection.len(),
25418            1,
25419            "Should have one selection after prev"
25420        );
25421        assert_ne!(
25422            prev_selection[0], function_next_selection[0],
25423            "Previous sibling selection should be different from next"
25424        );
25425    });
25426}
25427
25428#[track_caller]
25429fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
25430    editor
25431        .all_inlays(cx)
25432        .into_iter()
25433        .filter_map(|inlay| inlay.get_color())
25434        .map(Rgba::from)
25435        .collect()
25436}