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_cut_line_ends(cx: &mut TestAppContext) {
 6735    init_test(cx, |_| {});
 6736
 6737    let mut cx = EditorTestContext::new(cx).await;
 6738
 6739    cx.set_state(indoc! {"
 6740        The quick« brownˇ»
 6741        fox jumps overˇ
 6742        the lazy dog"});
 6743    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6744    cx.assert_editor_state(indoc! {"
 6745        The quickˇ
 6746        ˇthe lazy dog"});
 6747
 6748    cx.set_state(indoc! {"
 6749        The quick« brownˇ»
 6750        fox jumps overˇ
 6751        the lazy dog"});
 6752    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 6753    cx.assert_editor_state(indoc! {"
 6754        The quickˇ
 6755        fox jumps overˇthe lazy dog"});
 6756
 6757    cx.set_state(indoc! {"
 6758        The quick« brownˇ»
 6759        fox jumps overˇ
 6760        the lazy dog"});
 6761    cx.update_editor(|e, window, cx| {
 6762        e.cut_to_end_of_line(
 6763            &CutToEndOfLine {
 6764                stop_at_newlines: true,
 6765            },
 6766            window,
 6767            cx,
 6768        )
 6769    });
 6770    cx.assert_editor_state(indoc! {"
 6771        The quickˇ
 6772        fox jumps overˇ
 6773        the lazy dog"});
 6774
 6775    cx.set_state(indoc! {"
 6776        The quick« brownˇ»
 6777        fox jumps overˇ
 6778        the lazy dog"});
 6779    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 6780    cx.assert_editor_state(indoc! {"
 6781        The quickˇ
 6782        fox jumps overˇthe lazy dog"});
 6783}
 6784
 6785#[gpui::test]
 6786async fn test_clipboard(cx: &mut TestAppContext) {
 6787    init_test(cx, |_| {});
 6788
 6789    let mut cx = EditorTestContext::new(cx).await;
 6790
 6791    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 6792    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6793    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 6794
 6795    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 6796    cx.set_state("two ˇfour ˇsix ˇ");
 6797    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6798    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 6799
 6800    // Paste again but with only two cursors. Since the number of cursors doesn't
 6801    // match the number of slices in the clipboard, the entire clipboard text
 6802    // is pasted at each cursor.
 6803    cx.set_state("ˇtwo one✅ four three six five ˇ");
 6804    cx.update_editor(|e, window, cx| {
 6805        e.handle_input("( ", window, cx);
 6806        e.paste(&Paste, window, cx);
 6807        e.handle_input(") ", window, cx);
 6808    });
 6809    cx.assert_editor_state(
 6810        &([
 6811            "( one✅ ",
 6812            "three ",
 6813            "five ) ˇtwo one✅ four three six five ( one✅ ",
 6814            "three ",
 6815            "five ) ˇ",
 6816        ]
 6817        .join("\n")),
 6818    );
 6819
 6820    // Cut with three selections, one of which is full-line.
 6821    cx.set_state(indoc! {"
 6822        1«2ˇ»3
 6823        4ˇ567
 6824        «8ˇ»9"});
 6825    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6826    cx.assert_editor_state(indoc! {"
 6827        1ˇ3
 6828        ˇ9"});
 6829
 6830    // Paste with three selections, noticing how the copied selection that was full-line
 6831    // gets inserted before the second cursor.
 6832    cx.set_state(indoc! {"
 6833        1ˇ3
 6834 6835        «oˇ»ne"});
 6836    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6837    cx.assert_editor_state(indoc! {"
 6838        12ˇ3
 6839        4567
 6840 6841        8ˇne"});
 6842
 6843    // Copy with a single cursor only, which writes the whole line into the clipboard.
 6844    cx.set_state(indoc! {"
 6845        The quick brown
 6846        fox juˇmps over
 6847        the lazy dog"});
 6848    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6849    assert_eq!(
 6850        cx.read_from_clipboard()
 6851            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6852        Some("fox jumps over\n".to_string())
 6853    );
 6854
 6855    // Paste with three selections, noticing how the copied full-line selection is inserted
 6856    // before the empty selections but replaces the selection that is non-empty.
 6857    cx.set_state(indoc! {"
 6858        Tˇhe quick brown
 6859        «foˇ»x jumps over
 6860        tˇhe lazy dog"});
 6861    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6862    cx.assert_editor_state(indoc! {"
 6863        fox jumps over
 6864        Tˇhe quick brown
 6865        fox jumps over
 6866        ˇx jumps over
 6867        fox jumps over
 6868        tˇhe lazy dog"});
 6869}
 6870
 6871#[gpui::test]
 6872async fn test_copy_trim(cx: &mut TestAppContext) {
 6873    init_test(cx, |_| {});
 6874
 6875    let mut cx = EditorTestContext::new(cx).await;
 6876    cx.set_state(
 6877        r#"            «for selection in selections.iter() {
 6878            let mut start = selection.start;
 6879            let mut end = selection.end;
 6880            let is_entire_line = selection.is_empty();
 6881            if is_entire_line {
 6882                start = Point::new(start.row, 0);ˇ»
 6883                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6884            }
 6885        "#,
 6886    );
 6887    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6888    assert_eq!(
 6889        cx.read_from_clipboard()
 6890            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6891        Some(
 6892            "for selection in selections.iter() {
 6893            let mut start = selection.start;
 6894            let mut end = selection.end;
 6895            let is_entire_line = selection.is_empty();
 6896            if is_entire_line {
 6897                start = Point::new(start.row, 0);"
 6898                .to_string()
 6899        ),
 6900        "Regular copying preserves all indentation selected",
 6901    );
 6902    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6903    assert_eq!(
 6904        cx.read_from_clipboard()
 6905            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6906        Some(
 6907            "for selection in selections.iter() {
 6908let mut start = selection.start;
 6909let mut end = selection.end;
 6910let is_entire_line = selection.is_empty();
 6911if is_entire_line {
 6912    start = Point::new(start.row, 0);"
 6913                .to_string()
 6914        ),
 6915        "Copying with stripping should strip all leading whitespaces"
 6916    );
 6917
 6918    cx.set_state(
 6919        r#"       «     for selection in selections.iter() {
 6920            let mut start = selection.start;
 6921            let mut end = selection.end;
 6922            let is_entire_line = selection.is_empty();
 6923            if is_entire_line {
 6924                start = Point::new(start.row, 0);ˇ»
 6925                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6926            }
 6927        "#,
 6928    );
 6929    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6930    assert_eq!(
 6931        cx.read_from_clipboard()
 6932            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6933        Some(
 6934            "     for selection in selections.iter() {
 6935            let mut start = selection.start;
 6936            let mut end = selection.end;
 6937            let is_entire_line = selection.is_empty();
 6938            if is_entire_line {
 6939                start = Point::new(start.row, 0);"
 6940                .to_string()
 6941        ),
 6942        "Regular copying preserves all indentation selected",
 6943    );
 6944    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6945    assert_eq!(
 6946        cx.read_from_clipboard()
 6947            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6948        Some(
 6949            "for selection in selections.iter() {
 6950let mut start = selection.start;
 6951let mut end = selection.end;
 6952let is_entire_line = selection.is_empty();
 6953if is_entire_line {
 6954    start = Point::new(start.row, 0);"
 6955                .to_string()
 6956        ),
 6957        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 6958    );
 6959
 6960    cx.set_state(
 6961        r#"       «ˇ     for selection in selections.iter() {
 6962            let mut start = selection.start;
 6963            let mut end = selection.end;
 6964            let is_entire_line = selection.is_empty();
 6965            if is_entire_line {
 6966                start = Point::new(start.row, 0);»
 6967                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6968            }
 6969        "#,
 6970    );
 6971    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6972    assert_eq!(
 6973        cx.read_from_clipboard()
 6974            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6975        Some(
 6976            "     for selection in selections.iter() {
 6977            let mut start = selection.start;
 6978            let mut end = selection.end;
 6979            let is_entire_line = selection.is_empty();
 6980            if is_entire_line {
 6981                start = Point::new(start.row, 0);"
 6982                .to_string()
 6983        ),
 6984        "Regular copying for reverse selection works the same",
 6985    );
 6986    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6987    assert_eq!(
 6988        cx.read_from_clipboard()
 6989            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6990        Some(
 6991            "for selection in selections.iter() {
 6992let mut start = selection.start;
 6993let mut end = selection.end;
 6994let is_entire_line = selection.is_empty();
 6995if is_entire_line {
 6996    start = Point::new(start.row, 0);"
 6997                .to_string()
 6998        ),
 6999        "Copying with stripping for reverse selection works the same"
 7000    );
 7001
 7002    cx.set_state(
 7003        r#"            for selection «in selections.iter() {
 7004            let mut start = selection.start;
 7005            let mut end = selection.end;
 7006            let is_entire_line = selection.is_empty();
 7007            if is_entire_line {
 7008                start = Point::new(start.row, 0);ˇ»
 7009                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7010            }
 7011        "#,
 7012    );
 7013    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7014    assert_eq!(
 7015        cx.read_from_clipboard()
 7016            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7017        Some(
 7018            "in selections.iter() {
 7019            let mut start = selection.start;
 7020            let mut end = selection.end;
 7021            let is_entire_line = selection.is_empty();
 7022            if is_entire_line {
 7023                start = Point::new(start.row, 0);"
 7024                .to_string()
 7025        ),
 7026        "When selecting past the indent, the copying works as usual",
 7027    );
 7028    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7029    assert_eq!(
 7030        cx.read_from_clipboard()
 7031            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7032        Some(
 7033            "in selections.iter() {
 7034            let mut start = selection.start;
 7035            let mut end = selection.end;
 7036            let is_entire_line = selection.is_empty();
 7037            if is_entire_line {
 7038                start = Point::new(start.row, 0);"
 7039                .to_string()
 7040        ),
 7041        "When selecting past the indent, nothing is trimmed"
 7042    );
 7043
 7044    cx.set_state(
 7045        r#"            «for selection in selections.iter() {
 7046            let mut start = selection.start;
 7047
 7048            let mut end = selection.end;
 7049            let is_entire_line = selection.is_empty();
 7050            if is_entire_line {
 7051                start = Point::new(start.row, 0);
 7052ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7053            }
 7054        "#,
 7055    );
 7056    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7057    assert_eq!(
 7058        cx.read_from_clipboard()
 7059            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7060        Some(
 7061            "for selection in selections.iter() {
 7062let mut start = selection.start;
 7063
 7064let mut end = selection.end;
 7065let is_entire_line = selection.is_empty();
 7066if is_entire_line {
 7067    start = Point::new(start.row, 0);
 7068"
 7069            .to_string()
 7070        ),
 7071        "Copying with stripping should ignore empty lines"
 7072    );
 7073}
 7074
 7075#[gpui::test]
 7076async fn test_paste_multiline(cx: &mut TestAppContext) {
 7077    init_test(cx, |_| {});
 7078
 7079    let mut cx = EditorTestContext::new(cx).await;
 7080    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7081
 7082    // Cut an indented block, without the leading whitespace.
 7083    cx.set_state(indoc! {"
 7084        const a: B = (
 7085            c(),
 7086            «d(
 7087                e,
 7088                f
 7089            )ˇ»
 7090        );
 7091    "});
 7092    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7093    cx.assert_editor_state(indoc! {"
 7094        const a: B = (
 7095            c(),
 7096            ˇ
 7097        );
 7098    "});
 7099
 7100    // Paste it at the same position.
 7101    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7102    cx.assert_editor_state(indoc! {"
 7103        const a: B = (
 7104            c(),
 7105            d(
 7106                e,
 7107                f
 7108 7109        );
 7110    "});
 7111
 7112    // Paste it at a line with a lower indent level.
 7113    cx.set_state(indoc! {"
 7114        ˇ
 7115        const a: B = (
 7116            c(),
 7117        );
 7118    "});
 7119    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7120    cx.assert_editor_state(indoc! {"
 7121        d(
 7122            e,
 7123            f
 7124 7125        const a: B = (
 7126            c(),
 7127        );
 7128    "});
 7129
 7130    // Cut an indented block, with the leading whitespace.
 7131    cx.set_state(indoc! {"
 7132        const a: B = (
 7133            c(),
 7134        «    d(
 7135                e,
 7136                f
 7137            )
 7138        ˇ»);
 7139    "});
 7140    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7141    cx.assert_editor_state(indoc! {"
 7142        const a: B = (
 7143            c(),
 7144        ˇ);
 7145    "});
 7146
 7147    // Paste it at the same position.
 7148    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7149    cx.assert_editor_state(indoc! {"
 7150        const a: B = (
 7151            c(),
 7152            d(
 7153                e,
 7154                f
 7155            )
 7156        ˇ);
 7157    "});
 7158
 7159    // Paste it at a line with a higher indent level.
 7160    cx.set_state(indoc! {"
 7161        const a: B = (
 7162            c(),
 7163            d(
 7164                e,
 7165 7166            )
 7167        );
 7168    "});
 7169    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7170    cx.assert_editor_state(indoc! {"
 7171        const a: B = (
 7172            c(),
 7173            d(
 7174                e,
 7175                f    d(
 7176                    e,
 7177                    f
 7178                )
 7179        ˇ
 7180            )
 7181        );
 7182    "});
 7183
 7184    // Copy an indented block, starting mid-line
 7185    cx.set_state(indoc! {"
 7186        const a: B = (
 7187            c(),
 7188            somethin«g(
 7189                e,
 7190                f
 7191            )ˇ»
 7192        );
 7193    "});
 7194    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7195
 7196    // Paste it on a line with a lower indent level
 7197    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 7198    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7199    cx.assert_editor_state(indoc! {"
 7200        const a: B = (
 7201            c(),
 7202            something(
 7203                e,
 7204                f
 7205            )
 7206        );
 7207        g(
 7208            e,
 7209            f
 7210"});
 7211}
 7212
 7213#[gpui::test]
 7214async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 7215    init_test(cx, |_| {});
 7216
 7217    cx.write_to_clipboard(ClipboardItem::new_string(
 7218        "    d(\n        e\n    );\n".into(),
 7219    ));
 7220
 7221    let mut cx = EditorTestContext::new(cx).await;
 7222    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7223
 7224    cx.set_state(indoc! {"
 7225        fn a() {
 7226            b();
 7227            if c() {
 7228                ˇ
 7229            }
 7230        }
 7231    "});
 7232
 7233    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7234    cx.assert_editor_state(indoc! {"
 7235        fn a() {
 7236            b();
 7237            if c() {
 7238                d(
 7239                    e
 7240                );
 7241        ˇ
 7242            }
 7243        }
 7244    "});
 7245
 7246    cx.set_state(indoc! {"
 7247        fn a() {
 7248            b();
 7249            ˇ
 7250        }
 7251    "});
 7252
 7253    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7254    cx.assert_editor_state(indoc! {"
 7255        fn a() {
 7256            b();
 7257            d(
 7258                e
 7259            );
 7260        ˇ
 7261        }
 7262    "});
 7263}
 7264
 7265#[gpui::test]
 7266fn test_select_all(cx: &mut TestAppContext) {
 7267    init_test(cx, |_| {});
 7268
 7269    let editor = cx.add_window(|window, cx| {
 7270        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 7271        build_editor(buffer, window, cx)
 7272    });
 7273    _ = editor.update(cx, |editor, window, cx| {
 7274        editor.select_all(&SelectAll, window, cx);
 7275        assert_eq!(
 7276            editor.selections.display_ranges(cx),
 7277            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 7278        );
 7279    });
 7280}
 7281
 7282#[gpui::test]
 7283fn test_select_line(cx: &mut TestAppContext) {
 7284    init_test(cx, |_| {});
 7285
 7286    let editor = cx.add_window(|window, cx| {
 7287        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 7288        build_editor(buffer, window, cx)
 7289    });
 7290    _ = editor.update(cx, |editor, window, cx| {
 7291        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7292            s.select_display_ranges([
 7293                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7294                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7295                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7296                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 7297            ])
 7298        });
 7299        editor.select_line(&SelectLine, window, cx);
 7300        assert_eq!(
 7301            editor.selections.display_ranges(cx),
 7302            vec![
 7303                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 7304                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 7305            ]
 7306        );
 7307    });
 7308
 7309    _ = editor.update(cx, |editor, window, cx| {
 7310        editor.select_line(&SelectLine, window, cx);
 7311        assert_eq!(
 7312            editor.selections.display_ranges(cx),
 7313            vec![
 7314                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 7315                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 7316            ]
 7317        );
 7318    });
 7319
 7320    _ = editor.update(cx, |editor, window, cx| {
 7321        editor.select_line(&SelectLine, window, cx);
 7322        assert_eq!(
 7323            editor.selections.display_ranges(cx),
 7324            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 7325        );
 7326    });
 7327}
 7328
 7329#[gpui::test]
 7330async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 7331    init_test(cx, |_| {});
 7332    let mut cx = EditorTestContext::new(cx).await;
 7333
 7334    #[track_caller]
 7335    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 7336        cx.set_state(initial_state);
 7337        cx.update_editor(|e, window, cx| {
 7338            e.split_selection_into_lines(&Default::default(), window, cx)
 7339        });
 7340        cx.assert_editor_state(expected_state);
 7341    }
 7342
 7343    // Selection starts and ends at the middle of lines, left-to-right
 7344    test(
 7345        &mut cx,
 7346        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 7347        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7348    );
 7349    // Same thing, right-to-left
 7350    test(
 7351        &mut cx,
 7352        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 7353        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7354    );
 7355
 7356    // Whole buffer, left-to-right, last line *doesn't* end with newline
 7357    test(
 7358        &mut cx,
 7359        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 7360        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7361    );
 7362    // Same thing, right-to-left
 7363    test(
 7364        &mut cx,
 7365        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 7366        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7367    );
 7368
 7369    // Whole buffer, left-to-right, last line ends with newline
 7370    test(
 7371        &mut cx,
 7372        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 7373        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7374    );
 7375    // Same thing, right-to-left
 7376    test(
 7377        &mut cx,
 7378        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 7379        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7380    );
 7381
 7382    // Starts at the end of a line, ends at the start of another
 7383    test(
 7384        &mut cx,
 7385        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 7386        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 7387    );
 7388}
 7389
 7390#[gpui::test]
 7391async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 7392    init_test(cx, |_| {});
 7393
 7394    let editor = cx.add_window(|window, cx| {
 7395        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 7396        build_editor(buffer, window, cx)
 7397    });
 7398
 7399    // setup
 7400    _ = editor.update(cx, |editor, window, cx| {
 7401        editor.fold_creases(
 7402            vec![
 7403                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 7404                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 7405                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 7406            ],
 7407            true,
 7408            window,
 7409            cx,
 7410        );
 7411        assert_eq!(
 7412            editor.display_text(cx),
 7413            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7414        );
 7415    });
 7416
 7417    _ = editor.update(cx, |editor, window, cx| {
 7418        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7419            s.select_display_ranges([
 7420                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7421                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7422                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7423                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 7424            ])
 7425        });
 7426        editor.split_selection_into_lines(&Default::default(), window, cx);
 7427        assert_eq!(
 7428            editor.display_text(cx),
 7429            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7430        );
 7431    });
 7432    EditorTestContext::for_editor(editor, cx)
 7433        .await
 7434        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 7435
 7436    _ = editor.update(cx, |editor, window, cx| {
 7437        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7438            s.select_display_ranges([
 7439                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 7440            ])
 7441        });
 7442        editor.split_selection_into_lines(&Default::default(), window, cx);
 7443        assert_eq!(
 7444            editor.display_text(cx),
 7445            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 7446        );
 7447        assert_eq!(
 7448            editor.selections.display_ranges(cx),
 7449            [
 7450                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 7451                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 7452                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 7453                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 7454                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 7455                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 7456                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 7457            ]
 7458        );
 7459    });
 7460    EditorTestContext::for_editor(editor, cx)
 7461        .await
 7462        .assert_editor_state(
 7463            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 7464        );
 7465}
 7466
 7467#[gpui::test]
 7468async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 7469    init_test(cx, |_| {});
 7470
 7471    let mut cx = EditorTestContext::new(cx).await;
 7472
 7473    cx.set_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.add_selection_above(&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_above(&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.undo_selection(&Default::default(), window, cx);
 7523    });
 7524
 7525    cx.assert_editor_state(indoc!(
 7526        r#"abcˇ
 7527           defˇghi
 7528
 7529           jk
 7530           nlmo
 7531           "#
 7532    ));
 7533
 7534    cx.update_editor(|editor, window, cx| {
 7535        editor.redo_selection(&Default::default(), window, cx);
 7536    });
 7537
 7538    cx.assert_editor_state(indoc!(
 7539        r#"abc
 7540           defˇghi
 7541
 7542           jk
 7543           nlmo
 7544           "#
 7545    ));
 7546
 7547    cx.update_editor(|editor, window, cx| {
 7548        editor.add_selection_below(&Default::default(), window, cx);
 7549    });
 7550
 7551    cx.assert_editor_state(indoc!(
 7552        r#"abc
 7553           defˇghi
 7554           ˇ
 7555           jk
 7556           nlmo
 7557           "#
 7558    ));
 7559
 7560    cx.update_editor(|editor, window, cx| {
 7561        editor.add_selection_below(&Default::default(), window, cx);
 7562    });
 7563
 7564    cx.assert_editor_state(indoc!(
 7565        r#"abc
 7566           defˇghi
 7567           ˇ
 7568           jkˇ
 7569           nlmo
 7570           "#
 7571    ));
 7572
 7573    cx.update_editor(|editor, window, cx| {
 7574        editor.add_selection_below(&Default::default(), window, cx);
 7575    });
 7576
 7577    cx.assert_editor_state(indoc!(
 7578        r#"abc
 7579           defˇghi
 7580           ˇ
 7581           jkˇ
 7582           nlmˇo
 7583           "#
 7584    ));
 7585
 7586    cx.update_editor(|editor, window, cx| {
 7587        editor.add_selection_below(&Default::default(), window, cx);
 7588    });
 7589
 7590    cx.assert_editor_state(indoc!(
 7591        r#"abc
 7592           defˇghi
 7593           ˇ
 7594           jkˇ
 7595           nlmˇo
 7596           ˇ"#
 7597    ));
 7598
 7599    // change selections
 7600    cx.set_state(indoc!(
 7601        r#"abc
 7602           def«ˇg»hi
 7603
 7604           jk
 7605           nlmo
 7606           "#
 7607    ));
 7608
 7609    cx.update_editor(|editor, window, cx| {
 7610        editor.add_selection_below(&Default::default(), window, cx);
 7611    });
 7612
 7613    cx.assert_editor_state(indoc!(
 7614        r#"abc
 7615           def«ˇg»hi
 7616
 7617           jk
 7618           nlm«ˇo»
 7619           "#
 7620    ));
 7621
 7622    cx.update_editor(|editor, window, cx| {
 7623        editor.add_selection_below(&Default::default(), window, cx);
 7624    });
 7625
 7626    cx.assert_editor_state(indoc!(
 7627        r#"abc
 7628           def«ˇg»hi
 7629
 7630           jk
 7631           nlm«ˇo»
 7632           "#
 7633    ));
 7634
 7635    cx.update_editor(|editor, window, cx| {
 7636        editor.add_selection_above(&Default::default(), window, cx);
 7637    });
 7638
 7639    cx.assert_editor_state(indoc!(
 7640        r#"abc
 7641           def«ˇg»hi
 7642
 7643           jk
 7644           nlmo
 7645           "#
 7646    ));
 7647
 7648    cx.update_editor(|editor, window, cx| {
 7649        editor.add_selection_above(&Default::default(), window, cx);
 7650    });
 7651
 7652    cx.assert_editor_state(indoc!(
 7653        r#"abc
 7654           def«ˇg»hi
 7655
 7656           jk
 7657           nlmo
 7658           "#
 7659    ));
 7660
 7661    // Change selections again
 7662    cx.set_state(indoc!(
 7663        r#"a«bc
 7664           defgˇ»hi
 7665
 7666           jk
 7667           nlmo
 7668           "#
 7669    ));
 7670
 7671    cx.update_editor(|editor, window, cx| {
 7672        editor.add_selection_below(&Default::default(), window, cx);
 7673    });
 7674
 7675    cx.assert_editor_state(indoc!(
 7676        r#"a«bcˇ»
 7677           d«efgˇ»hi
 7678
 7679           j«kˇ»
 7680           nlmo
 7681           "#
 7682    ));
 7683
 7684    cx.update_editor(|editor, window, cx| {
 7685        editor.add_selection_below(&Default::default(), window, cx);
 7686    });
 7687    cx.assert_editor_state(indoc!(
 7688        r#"a«bcˇ»
 7689           d«efgˇ»hi
 7690
 7691           j«kˇ»
 7692           n«lmoˇ»
 7693           "#
 7694    ));
 7695    cx.update_editor(|editor, window, cx| {
 7696        editor.add_selection_above(&Default::default(), window, cx);
 7697    });
 7698
 7699    cx.assert_editor_state(indoc!(
 7700        r#"a«bcˇ»
 7701           d«efgˇ»hi
 7702
 7703           j«kˇ»
 7704           nlmo
 7705           "#
 7706    ));
 7707
 7708    // Change selections again
 7709    cx.set_state(indoc!(
 7710        r#"abc
 7711           d«ˇefghi
 7712
 7713           jk
 7714           nlm»o
 7715           "#
 7716    ));
 7717
 7718    cx.update_editor(|editor, window, cx| {
 7719        editor.add_selection_above(&Default::default(), window, cx);
 7720    });
 7721
 7722    cx.assert_editor_state(indoc!(
 7723        r#"a«ˇbc»
 7724           d«ˇef»ghi
 7725
 7726           j«ˇk»
 7727           n«ˇlm»o
 7728           "#
 7729    ));
 7730
 7731    cx.update_editor(|editor, window, cx| {
 7732        editor.add_selection_below(&Default::default(), window, cx);
 7733    });
 7734
 7735    cx.assert_editor_state(indoc!(
 7736        r#"abc
 7737           d«ˇef»ghi
 7738
 7739           j«ˇk»
 7740           n«ˇlm»o
 7741           "#
 7742    ));
 7743}
 7744
 7745#[gpui::test]
 7746async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 7747    init_test(cx, |_| {});
 7748    let mut cx = EditorTestContext::new(cx).await;
 7749
 7750    cx.set_state(indoc!(
 7751        r#"line onˇe
 7752           liˇne two
 7753           line three
 7754           line four"#
 7755    ));
 7756
 7757    cx.update_editor(|editor, window, cx| {
 7758        editor.add_selection_below(&Default::default(), window, cx);
 7759    });
 7760
 7761    // test multiple cursors expand in the same direction
 7762    cx.assert_editor_state(indoc!(
 7763        r#"line onˇe
 7764           liˇne twˇo
 7765           liˇne three
 7766           line four"#
 7767    ));
 7768
 7769    cx.update_editor(|editor, window, cx| {
 7770        editor.add_selection_below(&Default::default(), window, cx);
 7771    });
 7772
 7773    cx.update_editor(|editor, window, cx| {
 7774        editor.add_selection_below(&Default::default(), window, cx);
 7775    });
 7776
 7777    // test multiple cursors expand below overflow
 7778    cx.assert_editor_state(indoc!(
 7779        r#"line onˇe
 7780           liˇne twˇo
 7781           liˇne thˇree
 7782           liˇne foˇur"#
 7783    ));
 7784
 7785    cx.update_editor(|editor, window, cx| {
 7786        editor.add_selection_above(&Default::default(), window, cx);
 7787    });
 7788
 7789    // test multiple cursors retrieves back correctly
 7790    cx.assert_editor_state(indoc!(
 7791        r#"line onˇe
 7792           liˇne twˇo
 7793           liˇne thˇree
 7794           line four"#
 7795    ));
 7796
 7797    cx.update_editor(|editor, window, cx| {
 7798        editor.add_selection_above(&Default::default(), window, cx);
 7799    });
 7800
 7801    cx.update_editor(|editor, window, cx| {
 7802        editor.add_selection_above(&Default::default(), window, cx);
 7803    });
 7804
 7805    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 7806    cx.assert_editor_state(indoc!(
 7807        r#"liˇne onˇe
 7808           liˇne two
 7809           line three
 7810           line four"#
 7811    ));
 7812
 7813    cx.update_editor(|editor, window, cx| {
 7814        editor.undo_selection(&Default::default(), window, cx);
 7815    });
 7816
 7817    // test undo
 7818    cx.assert_editor_state(indoc!(
 7819        r#"line onˇe
 7820           liˇne twˇo
 7821           line three
 7822           line four"#
 7823    ));
 7824
 7825    cx.update_editor(|editor, window, cx| {
 7826        editor.redo_selection(&Default::default(), window, cx);
 7827    });
 7828
 7829    // test redo
 7830    cx.assert_editor_state(indoc!(
 7831        r#"liˇne onˇe
 7832           liˇne two
 7833           line three
 7834           line four"#
 7835    ));
 7836
 7837    cx.set_state(indoc!(
 7838        r#"abcd
 7839           ef«ghˇ»
 7840           ijkl
 7841           «mˇ»nop"#
 7842    ));
 7843
 7844    cx.update_editor(|editor, window, cx| {
 7845        editor.add_selection_above(&Default::default(), window, cx);
 7846    });
 7847
 7848    // test multiple selections expand in the same direction
 7849    cx.assert_editor_state(indoc!(
 7850        r#"ab«cdˇ»
 7851           ef«ghˇ»
 7852           «iˇ»jkl
 7853           «mˇ»nop"#
 7854    ));
 7855
 7856    cx.update_editor(|editor, window, cx| {
 7857        editor.add_selection_above(&Default::default(), window, cx);
 7858    });
 7859
 7860    // test multiple selection upward overflow
 7861    cx.assert_editor_state(indoc!(
 7862        r#"ab«cdˇ»
 7863           «eˇ»f«ghˇ»
 7864           «iˇ»jkl
 7865           «mˇ»nop"#
 7866    ));
 7867
 7868    cx.update_editor(|editor, window, cx| {
 7869        editor.add_selection_below(&Default::default(), window, cx);
 7870    });
 7871
 7872    // test multiple selection retrieves back correctly
 7873    cx.assert_editor_state(indoc!(
 7874        r#"abcd
 7875           ef«ghˇ»
 7876           «iˇ»jkl
 7877           «mˇ»nop"#
 7878    ));
 7879
 7880    cx.update_editor(|editor, window, cx| {
 7881        editor.add_selection_below(&Default::default(), window, cx);
 7882    });
 7883
 7884    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 7885    cx.assert_editor_state(indoc!(
 7886        r#"abcd
 7887           ef«ghˇ»
 7888           ij«klˇ»
 7889           «mˇ»nop"#
 7890    ));
 7891
 7892    cx.update_editor(|editor, window, cx| {
 7893        editor.undo_selection(&Default::default(), window, cx);
 7894    });
 7895
 7896    // test undo
 7897    cx.assert_editor_state(indoc!(
 7898        r#"abcd
 7899           ef«ghˇ»
 7900           «iˇ»jkl
 7901           «mˇ»nop"#
 7902    ));
 7903
 7904    cx.update_editor(|editor, window, cx| {
 7905        editor.redo_selection(&Default::default(), window, cx);
 7906    });
 7907
 7908    // test redo
 7909    cx.assert_editor_state(indoc!(
 7910        r#"abcd
 7911           ef«ghˇ»
 7912           ij«klˇ»
 7913           «mˇ»nop"#
 7914    ));
 7915}
 7916
 7917#[gpui::test]
 7918async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 7919    init_test(cx, |_| {});
 7920    let mut cx = EditorTestContext::new(cx).await;
 7921
 7922    cx.set_state(indoc!(
 7923        r#"line onˇe
 7924           liˇne two
 7925           line three
 7926           line four"#
 7927    ));
 7928
 7929    cx.update_editor(|editor, window, cx| {
 7930        editor.add_selection_below(&Default::default(), window, cx);
 7931        editor.add_selection_below(&Default::default(), window, cx);
 7932        editor.add_selection_below(&Default::default(), window, cx);
 7933    });
 7934
 7935    // initial state with two multi cursor groups
 7936    cx.assert_editor_state(indoc!(
 7937        r#"line onˇe
 7938           liˇne twˇo
 7939           liˇne thˇree
 7940           liˇne foˇur"#
 7941    ));
 7942
 7943    // add single cursor in middle - simulate opt click
 7944    cx.update_editor(|editor, window, cx| {
 7945        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 7946        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 7947        editor.end_selection(window, cx);
 7948    });
 7949
 7950    cx.assert_editor_state(indoc!(
 7951        r#"line onˇe
 7952           liˇne twˇo
 7953           liˇneˇ thˇree
 7954           liˇne foˇur"#
 7955    ));
 7956
 7957    cx.update_editor(|editor, window, cx| {
 7958        editor.add_selection_above(&Default::default(), window, cx);
 7959    });
 7960
 7961    // test new added selection expands above and existing selection shrinks
 7962    cx.assert_editor_state(indoc!(
 7963        r#"line onˇe
 7964           liˇneˇ twˇo
 7965           liˇneˇ thˇree
 7966           line four"#
 7967    ));
 7968
 7969    cx.update_editor(|editor, window, cx| {
 7970        editor.add_selection_above(&Default::default(), window, cx);
 7971    });
 7972
 7973    // test new added selection expands above and existing selection shrinks
 7974    cx.assert_editor_state(indoc!(
 7975        r#"lineˇ onˇe
 7976           liˇneˇ twˇo
 7977           lineˇ three
 7978           line four"#
 7979    ));
 7980
 7981    // intial state with two selection groups
 7982    cx.set_state(indoc!(
 7983        r#"abcd
 7984           ef«ghˇ»
 7985           ijkl
 7986           «mˇ»nop"#
 7987    ));
 7988
 7989    cx.update_editor(|editor, window, cx| {
 7990        editor.add_selection_above(&Default::default(), window, cx);
 7991        editor.add_selection_above(&Default::default(), window, cx);
 7992    });
 7993
 7994    cx.assert_editor_state(indoc!(
 7995        r#"ab«cdˇ»
 7996           «eˇ»f«ghˇ»
 7997           «iˇ»jkl
 7998           «mˇ»nop"#
 7999    ));
 8000
 8001    // add single selection in middle - simulate opt drag
 8002    cx.update_editor(|editor, window, cx| {
 8003        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 8004        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 8005        editor.update_selection(
 8006            DisplayPoint::new(DisplayRow(2), 4),
 8007            0,
 8008            gpui::Point::<f32>::default(),
 8009            window,
 8010            cx,
 8011        );
 8012        editor.end_selection(window, cx);
 8013    });
 8014
 8015    cx.assert_editor_state(indoc!(
 8016        r#"ab«cdˇ»
 8017           «eˇ»f«ghˇ»
 8018           «iˇ»jk«lˇ»
 8019           «mˇ»nop"#
 8020    ));
 8021
 8022    cx.update_editor(|editor, window, cx| {
 8023        editor.add_selection_below(&Default::default(), window, cx);
 8024    });
 8025
 8026    // test new added selection expands below, others shrinks from above
 8027    cx.assert_editor_state(indoc!(
 8028        r#"abcd
 8029           ef«ghˇ»
 8030           «iˇ»jk«lˇ»
 8031           «mˇ»no«pˇ»"#
 8032    ));
 8033}
 8034
 8035#[gpui::test]
 8036async fn test_select_next(cx: &mut TestAppContext) {
 8037    init_test(cx, |_| {});
 8038
 8039    let mut cx = EditorTestContext::new(cx).await;
 8040    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8041
 8042    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8043        .unwrap();
 8044    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8045
 8046    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8047        .unwrap();
 8048    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8049
 8050    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8051    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8052
 8053    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8054    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8055
 8056    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8057        .unwrap();
 8058    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8059
 8060    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8061        .unwrap();
 8062    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8063
 8064    // Test selection direction should be preserved
 8065    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8066
 8067    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8068        .unwrap();
 8069    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 8070}
 8071
 8072#[gpui::test]
 8073async fn test_select_all_matches(cx: &mut TestAppContext) {
 8074    init_test(cx, |_| {});
 8075
 8076    let mut cx = EditorTestContext::new(cx).await;
 8077
 8078    // Test caret-only selections
 8079    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8080    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8081        .unwrap();
 8082    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8083
 8084    // Test left-to-right selections
 8085    cx.set_state("abc\n«abcˇ»\nabc");
 8086    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8087        .unwrap();
 8088    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 8089
 8090    // Test right-to-left selections
 8091    cx.set_state("abc\n«ˇabc»\nabc");
 8092    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8093        .unwrap();
 8094    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 8095
 8096    // Test selecting whitespace with caret selection
 8097    cx.set_state("abc\nˇ   abc\nabc");
 8098    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8099        .unwrap();
 8100    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 8101
 8102    // Test selecting whitespace with left-to-right selection
 8103    cx.set_state("abc\n«ˇ  »abc\nabc");
 8104    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8105        .unwrap();
 8106    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 8107
 8108    // Test no matches with right-to-left selection
 8109    cx.set_state("abc\n«  ˇ»abc\nabc");
 8110    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8111        .unwrap();
 8112    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 8113
 8114    // Test with a single word and clip_at_line_ends=true (#29823)
 8115    cx.set_state("aˇbc");
 8116    cx.update_editor(|e, window, cx| {
 8117        e.set_clip_at_line_ends(true, cx);
 8118        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8119        e.set_clip_at_line_ends(false, cx);
 8120    });
 8121    cx.assert_editor_state("«abcˇ»");
 8122}
 8123
 8124#[gpui::test]
 8125async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 8126    init_test(cx, |_| {});
 8127
 8128    let mut cx = EditorTestContext::new(cx).await;
 8129
 8130    let large_body_1 = "\nd".repeat(200);
 8131    let large_body_2 = "\ne".repeat(200);
 8132
 8133    cx.set_state(&format!(
 8134        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 8135    ));
 8136    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 8137        let scroll_position = editor.scroll_position(cx);
 8138        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 8139        scroll_position
 8140    });
 8141
 8142    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8143        .unwrap();
 8144    cx.assert_editor_state(&format!(
 8145        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 8146    ));
 8147    let scroll_position_after_selection =
 8148        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 8149    assert_eq!(
 8150        initial_scroll_position, scroll_position_after_selection,
 8151        "Scroll position should not change after selecting all matches"
 8152    );
 8153}
 8154
 8155#[gpui::test]
 8156async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 8157    init_test(cx, |_| {});
 8158
 8159    let mut cx = EditorLspTestContext::new_rust(
 8160        lsp::ServerCapabilities {
 8161            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8162            ..Default::default()
 8163        },
 8164        cx,
 8165    )
 8166    .await;
 8167
 8168    cx.set_state(indoc! {"
 8169        line 1
 8170        line 2
 8171        linˇe 3
 8172        line 4
 8173        line 5
 8174    "});
 8175
 8176    // Make an edit
 8177    cx.update_editor(|editor, window, cx| {
 8178        editor.handle_input("X", window, cx);
 8179    });
 8180
 8181    // Move cursor to a different position
 8182    cx.update_editor(|editor, window, cx| {
 8183        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8184            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 8185        });
 8186    });
 8187
 8188    cx.assert_editor_state(indoc! {"
 8189        line 1
 8190        line 2
 8191        linXe 3
 8192        line 4
 8193        liˇne 5
 8194    "});
 8195
 8196    cx.lsp
 8197        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 8198            Ok(Some(vec![lsp::TextEdit::new(
 8199                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 8200                "PREFIX ".to_string(),
 8201            )]))
 8202        });
 8203
 8204    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 8205        .unwrap()
 8206        .await
 8207        .unwrap();
 8208
 8209    cx.assert_editor_state(indoc! {"
 8210        PREFIX line 1
 8211        line 2
 8212        linXe 3
 8213        line 4
 8214        liˇne 5
 8215    "});
 8216
 8217    // Undo formatting
 8218    cx.update_editor(|editor, window, cx| {
 8219        editor.undo(&Default::default(), window, cx);
 8220    });
 8221
 8222    // Verify cursor moved back to position after edit
 8223    cx.assert_editor_state(indoc! {"
 8224        line 1
 8225        line 2
 8226        linXˇe 3
 8227        line 4
 8228        line 5
 8229    "});
 8230}
 8231
 8232#[gpui::test]
 8233async fn test_undo_edit_prediction_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 8234    init_test(cx, |_| {});
 8235
 8236    let mut cx = EditorTestContext::new(cx).await;
 8237
 8238    let provider = cx.new(|_| FakeEditPredictionProvider::default());
 8239    cx.update_editor(|editor, window, cx| {
 8240        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 8241    });
 8242
 8243    cx.set_state(indoc! {"
 8244        line 1
 8245        line 2
 8246        linˇe 3
 8247        line 4
 8248        line 5
 8249        line 6
 8250        line 7
 8251        line 8
 8252        line 9
 8253        line 10
 8254    "});
 8255
 8256    let snapshot = cx.buffer_snapshot();
 8257    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 8258
 8259    cx.update(|_, cx| {
 8260        provider.update(cx, |provider, _| {
 8261            provider.set_edit_prediction(Some(edit_prediction::EditPrediction {
 8262                id: None,
 8263                edits: vec![(edit_position..edit_position, "X".into())],
 8264                edit_preview: None,
 8265            }))
 8266        })
 8267    });
 8268
 8269    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
 8270    cx.update_editor(|editor, window, cx| {
 8271        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 8272    });
 8273
 8274    cx.assert_editor_state(indoc! {"
 8275        line 1
 8276        line 2
 8277        lineXˇ 3
 8278        line 4
 8279        line 5
 8280        line 6
 8281        line 7
 8282        line 8
 8283        line 9
 8284        line 10
 8285    "});
 8286
 8287    cx.update_editor(|editor, window, cx| {
 8288        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8289            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 8290        });
 8291    });
 8292
 8293    cx.assert_editor_state(indoc! {"
 8294        line 1
 8295        line 2
 8296        lineX 3
 8297        line 4
 8298        line 5
 8299        line 6
 8300        line 7
 8301        line 8
 8302        line 9
 8303        liˇne 10
 8304    "});
 8305
 8306    cx.update_editor(|editor, window, cx| {
 8307        editor.undo(&Default::default(), window, cx);
 8308    });
 8309
 8310    cx.assert_editor_state(indoc! {"
 8311        line 1
 8312        line 2
 8313        lineˇ 3
 8314        line 4
 8315        line 5
 8316        line 6
 8317        line 7
 8318        line 8
 8319        line 9
 8320        line 10
 8321    "});
 8322}
 8323
 8324#[gpui::test]
 8325async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 8326    init_test(cx, |_| {});
 8327
 8328    let mut cx = EditorTestContext::new(cx).await;
 8329    cx.set_state(
 8330        r#"let foo = 2;
 8331lˇet foo = 2;
 8332let fooˇ = 2;
 8333let foo = 2;
 8334let foo = ˇ2;"#,
 8335    );
 8336
 8337    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8338        .unwrap();
 8339    cx.assert_editor_state(
 8340        r#"let foo = 2;
 8341«letˇ» foo = 2;
 8342let «fooˇ» = 2;
 8343let foo = 2;
 8344let foo = «2ˇ»;"#,
 8345    );
 8346
 8347    // noop for multiple selections with different contents
 8348    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8349        .unwrap();
 8350    cx.assert_editor_state(
 8351        r#"let foo = 2;
 8352«letˇ» foo = 2;
 8353let «fooˇ» = 2;
 8354let foo = 2;
 8355let foo = «2ˇ»;"#,
 8356    );
 8357
 8358    // Test last selection direction should be preserved
 8359    cx.set_state(
 8360        r#"let foo = 2;
 8361let foo = 2;
 8362let «fooˇ» = 2;
 8363let «ˇfoo» = 2;
 8364let foo = 2;"#,
 8365    );
 8366
 8367    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8368        .unwrap();
 8369    cx.assert_editor_state(
 8370        r#"let foo = 2;
 8371let foo = 2;
 8372let «fooˇ» = 2;
 8373let «ˇfoo» = 2;
 8374let «ˇfoo» = 2;"#,
 8375    );
 8376}
 8377
 8378#[gpui::test]
 8379async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 8380    init_test(cx, |_| {});
 8381
 8382    let mut cx =
 8383        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 8384
 8385    cx.assert_editor_state(indoc! {"
 8386        ˇbbb
 8387        ccc
 8388
 8389        bbb
 8390        ccc
 8391        "});
 8392    cx.dispatch_action(SelectPrevious::default());
 8393    cx.assert_editor_state(indoc! {"
 8394                «bbbˇ»
 8395                ccc
 8396
 8397                bbb
 8398                ccc
 8399                "});
 8400    cx.dispatch_action(SelectPrevious::default());
 8401    cx.assert_editor_state(indoc! {"
 8402                «bbbˇ»
 8403                ccc
 8404
 8405                «bbbˇ»
 8406                ccc
 8407                "});
 8408}
 8409
 8410#[gpui::test]
 8411async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 8412    init_test(cx, |_| {});
 8413
 8414    let mut cx = EditorTestContext::new(cx).await;
 8415    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8416
 8417    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8418        .unwrap();
 8419    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8420
 8421    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8422        .unwrap();
 8423    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8424
 8425    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8426    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8427
 8428    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8429    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8430
 8431    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8432        .unwrap();
 8433    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 8434
 8435    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8436        .unwrap();
 8437    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8438}
 8439
 8440#[gpui::test]
 8441async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 8442    init_test(cx, |_| {});
 8443
 8444    let mut cx = EditorTestContext::new(cx).await;
 8445    cx.set_state("");
 8446
 8447    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8448        .unwrap();
 8449    cx.assert_editor_state("«aˇ»");
 8450    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8451        .unwrap();
 8452    cx.assert_editor_state("«aˇ»");
 8453}
 8454
 8455#[gpui::test]
 8456async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 8457    init_test(cx, |_| {});
 8458
 8459    let mut cx = EditorTestContext::new(cx).await;
 8460    cx.set_state(
 8461        r#"let foo = 2;
 8462lˇet foo = 2;
 8463let fooˇ = 2;
 8464let foo = 2;
 8465let foo = ˇ2;"#,
 8466    );
 8467
 8468    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8469        .unwrap();
 8470    cx.assert_editor_state(
 8471        r#"let foo = 2;
 8472«letˇ» foo = 2;
 8473let «fooˇ» = 2;
 8474let foo = 2;
 8475let foo = «2ˇ»;"#,
 8476    );
 8477
 8478    // noop for multiple selections with different contents
 8479    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8480        .unwrap();
 8481    cx.assert_editor_state(
 8482        r#"let foo = 2;
 8483«letˇ» foo = 2;
 8484let «fooˇ» = 2;
 8485let foo = 2;
 8486let foo = «2ˇ»;"#,
 8487    );
 8488}
 8489
 8490#[gpui::test]
 8491async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 8492    init_test(cx, |_| {});
 8493
 8494    let mut cx = EditorTestContext::new(cx).await;
 8495    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8496
 8497    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8498        .unwrap();
 8499    // selection direction is preserved
 8500    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 8501
 8502    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8503        .unwrap();
 8504    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 8505
 8506    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8507    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 8508
 8509    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8510    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 8511
 8512    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8513        .unwrap();
 8514    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 8515
 8516    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8517        .unwrap();
 8518    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 8519}
 8520
 8521#[gpui::test]
 8522async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 8523    init_test(cx, |_| {});
 8524
 8525    let language = Arc::new(Language::new(
 8526        LanguageConfig::default(),
 8527        Some(tree_sitter_rust::LANGUAGE.into()),
 8528    ));
 8529
 8530    let text = r#"
 8531        use mod1::mod2::{mod3, mod4};
 8532
 8533        fn fn_1(param1: bool, param2: &str) {
 8534            let var1 = "text";
 8535        }
 8536    "#
 8537    .unindent();
 8538
 8539    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8540    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8541    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8542
 8543    editor
 8544        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8545        .await;
 8546
 8547    editor.update_in(cx, |editor, window, cx| {
 8548        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8549            s.select_display_ranges([
 8550                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 8551                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 8552                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 8553            ]);
 8554        });
 8555        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8556    });
 8557    editor.update(cx, |editor, cx| {
 8558        assert_text_with_selections(
 8559            editor,
 8560            indoc! {r#"
 8561                use mod1::mod2::{mod3, «mod4ˇ»};
 8562
 8563                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8564                    let var1 = "«ˇtext»";
 8565                }
 8566            "#},
 8567            cx,
 8568        );
 8569    });
 8570
 8571    editor.update_in(cx, |editor, window, cx| {
 8572        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8573    });
 8574    editor.update(cx, |editor, cx| {
 8575        assert_text_with_selections(
 8576            editor,
 8577            indoc! {r#"
 8578                use mod1::mod2::«{mod3, mod4}ˇ»;
 8579
 8580                «ˇfn fn_1(param1: bool, param2: &str) {
 8581                    let var1 = "text";
 8582 8583            "#},
 8584            cx,
 8585        );
 8586    });
 8587
 8588    editor.update_in(cx, |editor, window, cx| {
 8589        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8590    });
 8591    assert_eq!(
 8592        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 8593        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 8594    );
 8595
 8596    // Trying to expand the selected syntax node one more time has no effect.
 8597    editor.update_in(cx, |editor, window, cx| {
 8598        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8599    });
 8600    assert_eq!(
 8601        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 8602        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 8603    );
 8604
 8605    editor.update_in(cx, |editor, window, cx| {
 8606        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8607    });
 8608    editor.update(cx, |editor, cx| {
 8609        assert_text_with_selections(
 8610            editor,
 8611            indoc! {r#"
 8612                use mod1::mod2::«{mod3, mod4}ˇ»;
 8613
 8614                «ˇfn fn_1(param1: bool, param2: &str) {
 8615                    let var1 = "text";
 8616 8617            "#},
 8618            cx,
 8619        );
 8620    });
 8621
 8622    editor.update_in(cx, |editor, window, cx| {
 8623        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8624    });
 8625    editor.update(cx, |editor, cx| {
 8626        assert_text_with_selections(
 8627            editor,
 8628            indoc! {r#"
 8629                use mod1::mod2::{mod3, «mod4ˇ»};
 8630
 8631                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8632                    let var1 = "«ˇtext»";
 8633                }
 8634            "#},
 8635            cx,
 8636        );
 8637    });
 8638
 8639    editor.update_in(cx, |editor, window, cx| {
 8640        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8641    });
 8642    editor.update(cx, |editor, cx| {
 8643        assert_text_with_selections(
 8644            editor,
 8645            indoc! {r#"
 8646                use mod1::mod2::{mod3, moˇd4};
 8647
 8648                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 8649                    let var1 = "teˇxt";
 8650                }
 8651            "#},
 8652            cx,
 8653        );
 8654    });
 8655
 8656    // Trying to shrink the selected syntax node one more time has no effect.
 8657    editor.update_in(cx, |editor, window, cx| {
 8658        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8659    });
 8660    editor.update_in(cx, |editor, _, cx| {
 8661        assert_text_with_selections(
 8662            editor,
 8663            indoc! {r#"
 8664                use mod1::mod2::{mod3, moˇd4};
 8665
 8666                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 8667                    let var1 = "teˇxt";
 8668                }
 8669            "#},
 8670            cx,
 8671        );
 8672    });
 8673
 8674    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 8675    // a fold.
 8676    editor.update_in(cx, |editor, window, cx| {
 8677        editor.fold_creases(
 8678            vec![
 8679                Crease::simple(
 8680                    Point::new(0, 21)..Point::new(0, 24),
 8681                    FoldPlaceholder::test(),
 8682                ),
 8683                Crease::simple(
 8684                    Point::new(3, 20)..Point::new(3, 22),
 8685                    FoldPlaceholder::test(),
 8686                ),
 8687            ],
 8688            true,
 8689            window,
 8690            cx,
 8691        );
 8692        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8693    });
 8694    editor.update(cx, |editor, cx| {
 8695        assert_text_with_selections(
 8696            editor,
 8697            indoc! {r#"
 8698                use mod1::mod2::«{mod3, mod4}ˇ»;
 8699
 8700                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8701                    let var1 = "«ˇtext»";
 8702                }
 8703            "#},
 8704            cx,
 8705        );
 8706    });
 8707}
 8708
 8709#[gpui::test]
 8710async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 8711    init_test(cx, |_| {});
 8712
 8713    let language = Arc::new(Language::new(
 8714        LanguageConfig::default(),
 8715        Some(tree_sitter_rust::LANGUAGE.into()),
 8716    ));
 8717
 8718    let text = "let a = 2;";
 8719
 8720    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8721    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8722    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8723
 8724    editor
 8725        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8726        .await;
 8727
 8728    // Test case 1: Cursor at end of word
 8729    editor.update_in(cx, |editor, window, cx| {
 8730        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8731            s.select_display_ranges([
 8732                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 8733            ]);
 8734        });
 8735    });
 8736    editor.update(cx, |editor, cx| {
 8737        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 8738    });
 8739    editor.update_in(cx, |editor, window, cx| {
 8740        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8741    });
 8742    editor.update(cx, |editor, cx| {
 8743        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 8744    });
 8745    editor.update_in(cx, |editor, window, cx| {
 8746        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8747    });
 8748    editor.update(cx, |editor, cx| {
 8749        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 8750    });
 8751
 8752    // Test case 2: Cursor at end of statement
 8753    editor.update_in(cx, |editor, window, cx| {
 8754        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8755            s.select_display_ranges([
 8756                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 8757            ]);
 8758        });
 8759    });
 8760    editor.update(cx, |editor, cx| {
 8761        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 8762    });
 8763    editor.update_in(cx, |editor, window, cx| {
 8764        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8765    });
 8766    editor.update(cx, |editor, cx| {
 8767        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 8768    });
 8769}
 8770
 8771#[gpui::test]
 8772async fn test_select_larger_syntax_node_for_cursor_at_symbol(cx: &mut TestAppContext) {
 8773    init_test(cx, |_| {});
 8774
 8775    let language = Arc::new(Language::new(
 8776        LanguageConfig {
 8777            name: "JavaScript".into(),
 8778            ..Default::default()
 8779        },
 8780        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 8781    ));
 8782
 8783    let text = r#"
 8784        let a = {
 8785            key: "value",
 8786        };
 8787    "#
 8788    .unindent();
 8789
 8790    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8791    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8792    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8793
 8794    editor
 8795        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8796        .await;
 8797
 8798    // Test case 1: Cursor after '{'
 8799    editor.update_in(cx, |editor, window, cx| {
 8800        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8801            s.select_display_ranges([
 8802                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 9)
 8803            ]);
 8804        });
 8805    });
 8806    editor.update(cx, |editor, cx| {
 8807        assert_text_with_selections(
 8808            editor,
 8809            indoc! {r#"
 8810                let a = {ˇ
 8811                    key: "value",
 8812                };
 8813            "#},
 8814            cx,
 8815        );
 8816    });
 8817    editor.update_in(cx, |editor, window, cx| {
 8818        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8819    });
 8820    editor.update(cx, |editor, cx| {
 8821        assert_text_with_selections(
 8822            editor,
 8823            indoc! {r#"
 8824                let a = «ˇ{
 8825                    key: "value",
 8826                }»;
 8827            "#},
 8828            cx,
 8829        );
 8830    });
 8831
 8832    // Test case 2: Cursor after ':'
 8833    editor.update_in(cx, |editor, window, cx| {
 8834        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8835            s.select_display_ranges([
 8836                DisplayPoint::new(DisplayRow(1), 8)..DisplayPoint::new(DisplayRow(1), 8)
 8837            ]);
 8838        });
 8839    });
 8840    editor.update(cx, |editor, cx| {
 8841        assert_text_with_selections(
 8842            editor,
 8843            indoc! {r#"
 8844                let a = {
 8845                    key:ˇ "value",
 8846                };
 8847            "#},
 8848            cx,
 8849        );
 8850    });
 8851    editor.update_in(cx, |editor, window, cx| {
 8852        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8853    });
 8854    editor.update(cx, |editor, cx| {
 8855        assert_text_with_selections(
 8856            editor,
 8857            indoc! {r#"
 8858                let a = {
 8859                    «ˇkey: "value"»,
 8860                };
 8861            "#},
 8862            cx,
 8863        );
 8864    });
 8865    editor.update_in(cx, |editor, window, cx| {
 8866        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8867    });
 8868    editor.update(cx, |editor, cx| {
 8869        assert_text_with_selections(
 8870            editor,
 8871            indoc! {r#"
 8872                let a = «ˇ{
 8873                    key: "value",
 8874                }»;
 8875            "#},
 8876            cx,
 8877        );
 8878    });
 8879
 8880    // Test case 3: Cursor after ','
 8881    editor.update_in(cx, |editor, window, cx| {
 8882        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8883            s.select_display_ranges([
 8884                DisplayPoint::new(DisplayRow(1), 17)..DisplayPoint::new(DisplayRow(1), 17)
 8885            ]);
 8886        });
 8887    });
 8888    editor.update(cx, |editor, cx| {
 8889        assert_text_with_selections(
 8890            editor,
 8891            indoc! {r#"
 8892                let a = {
 8893                    key: "value",ˇ
 8894                };
 8895            "#},
 8896            cx,
 8897        );
 8898    });
 8899    editor.update_in(cx, |editor, window, cx| {
 8900        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8901    });
 8902    editor.update(cx, |editor, cx| {
 8903        assert_text_with_selections(
 8904            editor,
 8905            indoc! {r#"
 8906                let a = «ˇ{
 8907                    key: "value",
 8908                }»;
 8909            "#},
 8910            cx,
 8911        );
 8912    });
 8913
 8914    // Test case 4: Cursor after ';'
 8915    editor.update_in(cx, |editor, window, cx| {
 8916        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8917            s.select_display_ranges([
 8918                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)
 8919            ]);
 8920        });
 8921    });
 8922    editor.update(cx, |editor, cx| {
 8923        assert_text_with_selections(
 8924            editor,
 8925            indoc! {r#"
 8926                let a = {
 8927                    key: "value",
 8928                };ˇ
 8929            "#},
 8930            cx,
 8931        );
 8932    });
 8933    editor.update_in(cx, |editor, window, cx| {
 8934        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8935    });
 8936    editor.update(cx, |editor, cx| {
 8937        assert_text_with_selections(
 8938            editor,
 8939            indoc! {r#"
 8940                «ˇlet a = {
 8941                    key: "value",
 8942                };
 8943                »"#},
 8944            cx,
 8945        );
 8946    });
 8947}
 8948
 8949#[gpui::test]
 8950async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 8951    init_test(cx, |_| {});
 8952
 8953    let language = Arc::new(Language::new(
 8954        LanguageConfig::default(),
 8955        Some(tree_sitter_rust::LANGUAGE.into()),
 8956    ));
 8957
 8958    let text = r#"
 8959        use mod1::mod2::{mod3, mod4};
 8960
 8961        fn fn_1(param1: bool, param2: &str) {
 8962            let var1 = "hello world";
 8963        }
 8964    "#
 8965    .unindent();
 8966
 8967    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8968    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8969    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8970
 8971    editor
 8972        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8973        .await;
 8974
 8975    // Test 1: Cursor on a letter of a string word
 8976    editor.update_in(cx, |editor, window, cx| {
 8977        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8978            s.select_display_ranges([
 8979                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 8980            ]);
 8981        });
 8982    });
 8983    editor.update_in(cx, |editor, window, cx| {
 8984        assert_text_with_selections(
 8985            editor,
 8986            indoc! {r#"
 8987                use mod1::mod2::{mod3, mod4};
 8988
 8989                fn fn_1(param1: bool, param2: &str) {
 8990                    let var1 = "hˇello world";
 8991                }
 8992            "#},
 8993            cx,
 8994        );
 8995        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8996        assert_text_with_selections(
 8997            editor,
 8998            indoc! {r#"
 8999                use mod1::mod2::{mod3, mod4};
 9000
 9001                fn fn_1(param1: bool, param2: &str) {
 9002                    let var1 = "«ˇhello» world";
 9003                }
 9004            "#},
 9005            cx,
 9006        );
 9007    });
 9008
 9009    // Test 2: Partial selection within a word
 9010    editor.update_in(cx, |editor, window, cx| {
 9011        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9012            s.select_display_ranges([
 9013                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 9014            ]);
 9015        });
 9016    });
 9017    editor.update_in(cx, |editor, window, cx| {
 9018        assert_text_with_selections(
 9019            editor,
 9020            indoc! {r#"
 9021                use mod1::mod2::{mod3, mod4};
 9022
 9023                fn fn_1(param1: bool, param2: &str) {
 9024                    let var1 = "h«elˇ»lo world";
 9025                }
 9026            "#},
 9027            cx,
 9028        );
 9029        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9030        assert_text_with_selections(
 9031            editor,
 9032            indoc! {r#"
 9033                use mod1::mod2::{mod3, mod4};
 9034
 9035                fn fn_1(param1: bool, param2: &str) {
 9036                    let var1 = "«ˇhello» world";
 9037                }
 9038            "#},
 9039            cx,
 9040        );
 9041    });
 9042
 9043    // Test 3: Complete word already selected
 9044    editor.update_in(cx, |editor, window, cx| {
 9045        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9046            s.select_display_ranges([
 9047                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 9048            ]);
 9049        });
 9050    });
 9051    editor.update_in(cx, |editor, window, cx| {
 9052        assert_text_with_selections(
 9053            editor,
 9054            indoc! {r#"
 9055                use mod1::mod2::{mod3, mod4};
 9056
 9057                fn fn_1(param1: bool, param2: &str) {
 9058                    let var1 = "«helloˇ» world";
 9059                }
 9060            "#},
 9061            cx,
 9062        );
 9063        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9064        assert_text_with_selections(
 9065            editor,
 9066            indoc! {r#"
 9067                use mod1::mod2::{mod3, mod4};
 9068
 9069                fn fn_1(param1: bool, param2: &str) {
 9070                    let var1 = "«hello worldˇ»";
 9071                }
 9072            "#},
 9073            cx,
 9074        );
 9075    });
 9076
 9077    // Test 4: Selection spanning across words
 9078    editor.update_in(cx, |editor, window, cx| {
 9079        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9080            s.select_display_ranges([
 9081                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 9082            ]);
 9083        });
 9084    });
 9085    editor.update_in(cx, |editor, window, cx| {
 9086        assert_text_with_selections(
 9087            editor,
 9088            indoc! {r#"
 9089                use mod1::mod2::{mod3, mod4};
 9090
 9091                fn fn_1(param1: bool, param2: &str) {
 9092                    let var1 = "hel«lo woˇ»rld";
 9093                }
 9094            "#},
 9095            cx,
 9096        );
 9097        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9098        assert_text_with_selections(
 9099            editor,
 9100            indoc! {r#"
 9101                use mod1::mod2::{mod3, mod4};
 9102
 9103                fn fn_1(param1: bool, param2: &str) {
 9104                    let var1 = "«ˇhello world»";
 9105                }
 9106            "#},
 9107            cx,
 9108        );
 9109    });
 9110
 9111    // Test 5: Expansion beyond string
 9112    editor.update_in(cx, |editor, window, cx| {
 9113        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9114        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9115        assert_text_with_selections(
 9116            editor,
 9117            indoc! {r#"
 9118                use mod1::mod2::{mod3, mod4};
 9119
 9120                fn fn_1(param1: bool, param2: &str) {
 9121                    «ˇlet var1 = "hello world";»
 9122                }
 9123            "#},
 9124            cx,
 9125        );
 9126    });
 9127}
 9128
 9129#[gpui::test]
 9130async fn test_unwrap_syntax_nodes(cx: &mut gpui::TestAppContext) {
 9131    init_test(cx, |_| {});
 9132
 9133    let mut cx = EditorTestContext::new(cx).await;
 9134
 9135    let language = Arc::new(Language::new(
 9136        LanguageConfig::default(),
 9137        Some(tree_sitter_rust::LANGUAGE.into()),
 9138    ));
 9139
 9140    cx.update_buffer(|buffer, cx| {
 9141        buffer.set_language(Some(language), cx);
 9142    });
 9143
 9144    cx.set_state(indoc! { r#"use mod1::{mod2::{«mod3ˇ», mod4}, mod5::{mod6, «mod7ˇ»}};"# });
 9145    cx.update_editor(|editor, window, cx| {
 9146        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9147    });
 9148
 9149    cx.assert_editor_state(indoc! { r#"use mod1::{mod2::«mod3ˇ», mod5::«mod7ˇ»};"# });
 9150}
 9151
 9152#[gpui::test]
 9153async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 9154    init_test(cx, |_| {});
 9155
 9156    let base_text = r#"
 9157        impl A {
 9158            // this is an uncommitted comment
 9159
 9160            fn b() {
 9161                c();
 9162            }
 9163
 9164            // this is another uncommitted comment
 9165
 9166            fn d() {
 9167                // e
 9168                // f
 9169            }
 9170        }
 9171
 9172        fn g() {
 9173            // h
 9174        }
 9175    "#
 9176    .unindent();
 9177
 9178    let text = r#"
 9179        ˇimpl A {
 9180
 9181            fn b() {
 9182                c();
 9183            }
 9184
 9185            fn d() {
 9186                // e
 9187                // f
 9188            }
 9189        }
 9190
 9191        fn g() {
 9192            // h
 9193        }
 9194    "#
 9195    .unindent();
 9196
 9197    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9198    cx.set_state(&text);
 9199    cx.set_head_text(&base_text);
 9200    cx.update_editor(|editor, window, cx| {
 9201        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 9202    });
 9203
 9204    cx.assert_state_with_diff(
 9205        "
 9206        ˇimpl A {
 9207      -     // this is an uncommitted comment
 9208
 9209            fn b() {
 9210                c();
 9211            }
 9212
 9213      -     // this is another uncommitted comment
 9214      -
 9215            fn d() {
 9216                // e
 9217                // f
 9218            }
 9219        }
 9220
 9221        fn g() {
 9222            // h
 9223        }
 9224    "
 9225        .unindent(),
 9226    );
 9227
 9228    let expected_display_text = "
 9229        impl A {
 9230            // this is an uncommitted comment
 9231
 9232            fn b() {
 9233 9234            }
 9235
 9236            // this is another uncommitted comment
 9237
 9238            fn d() {
 9239 9240            }
 9241        }
 9242
 9243        fn g() {
 9244 9245        }
 9246        "
 9247    .unindent();
 9248
 9249    cx.update_editor(|editor, window, cx| {
 9250        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 9251        assert_eq!(editor.display_text(cx), expected_display_text);
 9252    });
 9253}
 9254
 9255#[gpui::test]
 9256async fn test_autoindent(cx: &mut TestAppContext) {
 9257    init_test(cx, |_| {});
 9258
 9259    let language = Arc::new(
 9260        Language::new(
 9261            LanguageConfig {
 9262                brackets: BracketPairConfig {
 9263                    pairs: vec![
 9264                        BracketPair {
 9265                            start: "{".to_string(),
 9266                            end: "}".to_string(),
 9267                            close: false,
 9268                            surround: false,
 9269                            newline: true,
 9270                        },
 9271                        BracketPair {
 9272                            start: "(".to_string(),
 9273                            end: ")".to_string(),
 9274                            close: false,
 9275                            surround: false,
 9276                            newline: true,
 9277                        },
 9278                    ],
 9279                    ..Default::default()
 9280                },
 9281                ..Default::default()
 9282            },
 9283            Some(tree_sitter_rust::LANGUAGE.into()),
 9284        )
 9285        .with_indents_query(
 9286            r#"
 9287                (_ "(" ")" @end) @indent
 9288                (_ "{" "}" @end) @indent
 9289            "#,
 9290        )
 9291        .unwrap(),
 9292    );
 9293
 9294    let text = "fn a() {}";
 9295
 9296    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9297    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9298    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9299    editor
 9300        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9301        .await;
 9302
 9303    editor.update_in(cx, |editor, window, cx| {
 9304        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9305            s.select_ranges([5..5, 8..8, 9..9])
 9306        });
 9307        editor.newline(&Newline, window, cx);
 9308        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 9309        assert_eq!(
 9310            editor.selections.ranges(cx),
 9311            &[
 9312                Point::new(1, 4)..Point::new(1, 4),
 9313                Point::new(3, 4)..Point::new(3, 4),
 9314                Point::new(5, 0)..Point::new(5, 0)
 9315            ]
 9316        );
 9317    });
 9318}
 9319
 9320#[gpui::test]
 9321async fn test_autoindent_disabled(cx: &mut TestAppContext) {
 9322    init_test(cx, |settings| settings.defaults.auto_indent = Some(false));
 9323
 9324    let language = Arc::new(
 9325        Language::new(
 9326            LanguageConfig {
 9327                brackets: BracketPairConfig {
 9328                    pairs: vec![
 9329                        BracketPair {
 9330                            start: "{".to_string(),
 9331                            end: "}".to_string(),
 9332                            close: false,
 9333                            surround: false,
 9334                            newline: true,
 9335                        },
 9336                        BracketPair {
 9337                            start: "(".to_string(),
 9338                            end: ")".to_string(),
 9339                            close: false,
 9340                            surround: false,
 9341                            newline: true,
 9342                        },
 9343                    ],
 9344                    ..Default::default()
 9345                },
 9346                ..Default::default()
 9347            },
 9348            Some(tree_sitter_rust::LANGUAGE.into()),
 9349        )
 9350        .with_indents_query(
 9351            r#"
 9352                (_ "(" ")" @end) @indent
 9353                (_ "{" "}" @end) @indent
 9354            "#,
 9355        )
 9356        .unwrap(),
 9357    );
 9358
 9359    let text = "fn a() {}";
 9360
 9361    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9362    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9363    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9364    editor
 9365        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9366        .await;
 9367
 9368    editor.update_in(cx, |editor, window, cx| {
 9369        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9370            s.select_ranges([5..5, 8..8, 9..9])
 9371        });
 9372        editor.newline(&Newline, window, cx);
 9373        assert_eq!(
 9374            editor.text(cx),
 9375            indoc!(
 9376                "
 9377                fn a(
 9378
 9379                ) {
 9380
 9381                }
 9382                "
 9383            )
 9384        );
 9385        assert_eq!(
 9386            editor.selections.ranges(cx),
 9387            &[
 9388                Point::new(1, 0)..Point::new(1, 0),
 9389                Point::new(3, 0)..Point::new(3, 0),
 9390                Point::new(5, 0)..Point::new(5, 0)
 9391            ]
 9392        );
 9393    });
 9394}
 9395
 9396#[gpui::test]
 9397async fn test_autoindent_disabled_with_nested_language(cx: &mut TestAppContext) {
 9398    init_test(cx, |settings| {
 9399        settings.defaults.auto_indent = Some(true);
 9400        settings.languages.0.insert(
 9401            "python".into(),
 9402            LanguageSettingsContent {
 9403                auto_indent: Some(false),
 9404                ..Default::default()
 9405            },
 9406        );
 9407    });
 9408
 9409    let mut cx = EditorTestContext::new(cx).await;
 9410
 9411    let injected_language = Arc::new(
 9412        Language::new(
 9413            LanguageConfig {
 9414                brackets: BracketPairConfig {
 9415                    pairs: vec![
 9416                        BracketPair {
 9417                            start: "{".to_string(),
 9418                            end: "}".to_string(),
 9419                            close: false,
 9420                            surround: false,
 9421                            newline: true,
 9422                        },
 9423                        BracketPair {
 9424                            start: "(".to_string(),
 9425                            end: ")".to_string(),
 9426                            close: true,
 9427                            surround: false,
 9428                            newline: true,
 9429                        },
 9430                    ],
 9431                    ..Default::default()
 9432                },
 9433                name: "python".into(),
 9434                ..Default::default()
 9435            },
 9436            Some(tree_sitter_python::LANGUAGE.into()),
 9437        )
 9438        .with_indents_query(
 9439            r#"
 9440                (_ "(" ")" @end) @indent
 9441                (_ "{" "}" @end) @indent
 9442            "#,
 9443        )
 9444        .unwrap(),
 9445    );
 9446
 9447    let language = Arc::new(
 9448        Language::new(
 9449            LanguageConfig {
 9450                brackets: BracketPairConfig {
 9451                    pairs: vec![
 9452                        BracketPair {
 9453                            start: "{".to_string(),
 9454                            end: "}".to_string(),
 9455                            close: false,
 9456                            surround: false,
 9457                            newline: true,
 9458                        },
 9459                        BracketPair {
 9460                            start: "(".to_string(),
 9461                            end: ")".to_string(),
 9462                            close: true,
 9463                            surround: false,
 9464                            newline: true,
 9465                        },
 9466                    ],
 9467                    ..Default::default()
 9468                },
 9469                name: LanguageName::new("rust"),
 9470                ..Default::default()
 9471            },
 9472            Some(tree_sitter_rust::LANGUAGE.into()),
 9473        )
 9474        .with_indents_query(
 9475            r#"
 9476                (_ "(" ")" @end) @indent
 9477                (_ "{" "}" @end) @indent
 9478            "#,
 9479        )
 9480        .unwrap()
 9481        .with_injection_query(
 9482            r#"
 9483            (macro_invocation
 9484                macro: (identifier) @_macro_name
 9485                (token_tree) @injection.content
 9486                (#set! injection.language "python"))
 9487           "#,
 9488        )
 9489        .unwrap(),
 9490    );
 9491
 9492    cx.language_registry().add(injected_language);
 9493    cx.language_registry().add(language.clone());
 9494
 9495    cx.update_buffer(|buffer, cx| {
 9496        buffer.set_language(Some(language), cx);
 9497    });
 9498
 9499    cx.set_state(r#"struct A {ˇ}"#);
 9500
 9501    cx.update_editor(|editor, window, cx| {
 9502        editor.newline(&Default::default(), window, cx);
 9503    });
 9504
 9505    cx.assert_editor_state(indoc!(
 9506        "struct A {
 9507            ˇ
 9508        }"
 9509    ));
 9510
 9511    cx.set_state(r#"select_biased!(ˇ)"#);
 9512
 9513    cx.update_editor(|editor, window, cx| {
 9514        editor.newline(&Default::default(), window, cx);
 9515        editor.handle_input("def ", window, cx);
 9516        editor.handle_input("(", window, cx);
 9517        editor.newline(&Default::default(), window, cx);
 9518        editor.handle_input("a", window, cx);
 9519    });
 9520
 9521    cx.assert_editor_state(indoc!(
 9522        "select_biased!(
 9523        def (
 9524 9525        )
 9526        )"
 9527    ));
 9528}
 9529
 9530#[gpui::test]
 9531async fn test_autoindent_selections(cx: &mut TestAppContext) {
 9532    init_test(cx, |_| {});
 9533
 9534    {
 9535        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9536        cx.set_state(indoc! {"
 9537            impl A {
 9538
 9539                fn b() {}
 9540
 9541            «fn c() {
 9542
 9543            }ˇ»
 9544            }
 9545        "});
 9546
 9547        cx.update_editor(|editor, window, cx| {
 9548            editor.autoindent(&Default::default(), window, cx);
 9549        });
 9550
 9551        cx.assert_editor_state(indoc! {"
 9552            impl A {
 9553
 9554                fn b() {}
 9555
 9556                «fn c() {
 9557
 9558                }ˇ»
 9559            }
 9560        "});
 9561    }
 9562
 9563    {
 9564        let mut cx = EditorTestContext::new_multibuffer(
 9565            cx,
 9566            [indoc! { "
 9567                impl A {
 9568                «
 9569                // a
 9570                fn b(){}
 9571                »
 9572                «
 9573                    }
 9574                    fn c(){}
 9575                »
 9576            "}],
 9577        );
 9578
 9579        let buffer = cx.update_editor(|editor, _, cx| {
 9580            let buffer = editor.buffer().update(cx, |buffer, _| {
 9581                buffer.all_buffers().iter().next().unwrap().clone()
 9582            });
 9583            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 9584            buffer
 9585        });
 9586
 9587        cx.run_until_parked();
 9588        cx.update_editor(|editor, window, cx| {
 9589            editor.select_all(&Default::default(), window, cx);
 9590            editor.autoindent(&Default::default(), window, cx)
 9591        });
 9592        cx.run_until_parked();
 9593
 9594        cx.update(|_, cx| {
 9595            assert_eq!(
 9596                buffer.read(cx).text(),
 9597                indoc! { "
 9598                    impl A {
 9599
 9600                        // a
 9601                        fn b(){}
 9602
 9603
 9604                    }
 9605                    fn c(){}
 9606
 9607                " }
 9608            )
 9609        });
 9610    }
 9611}
 9612
 9613#[gpui::test]
 9614async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 9615    init_test(cx, |_| {});
 9616
 9617    let mut cx = EditorTestContext::new(cx).await;
 9618
 9619    let language = Arc::new(Language::new(
 9620        LanguageConfig {
 9621            brackets: BracketPairConfig {
 9622                pairs: vec![
 9623                    BracketPair {
 9624                        start: "{".to_string(),
 9625                        end: "}".to_string(),
 9626                        close: true,
 9627                        surround: true,
 9628                        newline: true,
 9629                    },
 9630                    BracketPair {
 9631                        start: "(".to_string(),
 9632                        end: ")".to_string(),
 9633                        close: true,
 9634                        surround: true,
 9635                        newline: true,
 9636                    },
 9637                    BracketPair {
 9638                        start: "/*".to_string(),
 9639                        end: " */".to_string(),
 9640                        close: true,
 9641                        surround: true,
 9642                        newline: true,
 9643                    },
 9644                    BracketPair {
 9645                        start: "[".to_string(),
 9646                        end: "]".to_string(),
 9647                        close: false,
 9648                        surround: false,
 9649                        newline: true,
 9650                    },
 9651                    BracketPair {
 9652                        start: "\"".to_string(),
 9653                        end: "\"".to_string(),
 9654                        close: true,
 9655                        surround: true,
 9656                        newline: false,
 9657                    },
 9658                    BracketPair {
 9659                        start: "<".to_string(),
 9660                        end: ">".to_string(),
 9661                        close: false,
 9662                        surround: true,
 9663                        newline: true,
 9664                    },
 9665                ],
 9666                ..Default::default()
 9667            },
 9668            autoclose_before: "})]".to_string(),
 9669            ..Default::default()
 9670        },
 9671        Some(tree_sitter_rust::LANGUAGE.into()),
 9672    ));
 9673
 9674    cx.language_registry().add(language.clone());
 9675    cx.update_buffer(|buffer, cx| {
 9676        buffer.set_language(Some(language), cx);
 9677    });
 9678
 9679    cx.set_state(
 9680        &r#"
 9681            🏀ˇ
 9682            εˇ
 9683            ❤️ˇ
 9684        "#
 9685        .unindent(),
 9686    );
 9687
 9688    // autoclose multiple nested brackets at multiple cursors
 9689    cx.update_editor(|editor, window, cx| {
 9690        editor.handle_input("{", window, cx);
 9691        editor.handle_input("{", window, cx);
 9692        editor.handle_input("{", window, cx);
 9693    });
 9694    cx.assert_editor_state(
 9695        &"
 9696            🏀{{{ˇ}}}
 9697            ε{{{ˇ}}}
 9698            ❤️{{{ˇ}}}
 9699        "
 9700        .unindent(),
 9701    );
 9702
 9703    // insert a different closing bracket
 9704    cx.update_editor(|editor, window, cx| {
 9705        editor.handle_input(")", window, cx);
 9706    });
 9707    cx.assert_editor_state(
 9708        &"
 9709            🏀{{{)ˇ}}}
 9710            ε{{{)ˇ}}}
 9711            ❤️{{{)ˇ}}}
 9712        "
 9713        .unindent(),
 9714    );
 9715
 9716    // skip over the auto-closed brackets when typing a closing bracket
 9717    cx.update_editor(|editor, window, cx| {
 9718        editor.move_right(&MoveRight, window, cx);
 9719        editor.handle_input("}", window, cx);
 9720        editor.handle_input("}", window, cx);
 9721        editor.handle_input("}", window, cx);
 9722    });
 9723    cx.assert_editor_state(
 9724        &"
 9725            🏀{{{)}}}}ˇ
 9726            ε{{{)}}}}ˇ
 9727            ❤️{{{)}}}}ˇ
 9728        "
 9729        .unindent(),
 9730    );
 9731
 9732    // autoclose multi-character pairs
 9733    cx.set_state(
 9734        &"
 9735            ˇ
 9736            ˇ
 9737        "
 9738        .unindent(),
 9739    );
 9740    cx.update_editor(|editor, window, cx| {
 9741        editor.handle_input("/", window, cx);
 9742        editor.handle_input("*", window, cx);
 9743    });
 9744    cx.assert_editor_state(
 9745        &"
 9746            /*ˇ */
 9747            /*ˇ */
 9748        "
 9749        .unindent(),
 9750    );
 9751
 9752    // one cursor autocloses a multi-character pair, one cursor
 9753    // does not autoclose.
 9754    cx.set_state(
 9755        &"
 9756 9757            ˇ
 9758        "
 9759        .unindent(),
 9760    );
 9761    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
 9762    cx.assert_editor_state(
 9763        &"
 9764            /*ˇ */
 9765 9766        "
 9767        .unindent(),
 9768    );
 9769
 9770    // Don't autoclose if the next character isn't whitespace and isn't
 9771    // listed in the language's "autoclose_before" section.
 9772    cx.set_state("ˇa b");
 9773    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 9774    cx.assert_editor_state("{ˇa b");
 9775
 9776    // Don't autoclose if `close` is false for the bracket pair
 9777    cx.set_state("ˇ");
 9778    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
 9779    cx.assert_editor_state("");
 9780
 9781    // Surround with brackets if text is selected
 9782    cx.set_state("«aˇ» b");
 9783    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 9784    cx.assert_editor_state("{«aˇ»} b");
 9785
 9786    // Autoclose when not immediately after a word character
 9787    cx.set_state("a ˇ");
 9788    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9789    cx.assert_editor_state("a \"ˇ\"");
 9790
 9791    // Autoclose pair where the start and end characters are the same
 9792    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9793    cx.assert_editor_state("a \"\"ˇ");
 9794
 9795    // Don't autoclose when immediately after a word character
 9796    cx.set_state("");
 9797    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9798    cx.assert_editor_state("a\"ˇ");
 9799
 9800    // Do autoclose when after a non-word character
 9801    cx.set_state("");
 9802    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9803    cx.assert_editor_state("{\"ˇ\"");
 9804
 9805    // Non identical pairs autoclose regardless of preceding character
 9806    cx.set_state("");
 9807    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 9808    cx.assert_editor_state("a{ˇ}");
 9809
 9810    // Don't autoclose pair if autoclose is disabled
 9811    cx.set_state("ˇ");
 9812    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 9813    cx.assert_editor_state("");
 9814
 9815    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
 9816    cx.set_state("«aˇ» b");
 9817    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 9818    cx.assert_editor_state("<«aˇ»> b");
 9819}
 9820
 9821#[gpui::test]
 9822async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
 9823    init_test(cx, |settings| {
 9824        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 9825    });
 9826
 9827    let mut cx = EditorTestContext::new(cx).await;
 9828
 9829    let language = Arc::new(Language::new(
 9830        LanguageConfig {
 9831            brackets: BracketPairConfig {
 9832                pairs: vec![
 9833                    BracketPair {
 9834                        start: "{".to_string(),
 9835                        end: "}".to_string(),
 9836                        close: true,
 9837                        surround: true,
 9838                        newline: true,
 9839                    },
 9840                    BracketPair {
 9841                        start: "(".to_string(),
 9842                        end: ")".to_string(),
 9843                        close: true,
 9844                        surround: true,
 9845                        newline: true,
 9846                    },
 9847                    BracketPair {
 9848                        start: "[".to_string(),
 9849                        end: "]".to_string(),
 9850                        close: false,
 9851                        surround: false,
 9852                        newline: true,
 9853                    },
 9854                ],
 9855                ..Default::default()
 9856            },
 9857            autoclose_before: "})]".to_string(),
 9858            ..Default::default()
 9859        },
 9860        Some(tree_sitter_rust::LANGUAGE.into()),
 9861    ));
 9862
 9863    cx.language_registry().add(language.clone());
 9864    cx.update_buffer(|buffer, cx| {
 9865        buffer.set_language(Some(language), cx);
 9866    });
 9867
 9868    cx.set_state(
 9869        &"
 9870            ˇ
 9871            ˇ
 9872            ˇ
 9873        "
 9874        .unindent(),
 9875    );
 9876
 9877    // ensure only matching closing brackets are skipped over
 9878    cx.update_editor(|editor, window, cx| {
 9879        editor.handle_input("}", window, cx);
 9880        editor.move_left(&MoveLeft, window, cx);
 9881        editor.handle_input(")", window, cx);
 9882        editor.move_left(&MoveLeft, window, cx);
 9883    });
 9884    cx.assert_editor_state(
 9885        &"
 9886            ˇ)}
 9887            ˇ)}
 9888            ˇ)}
 9889        "
 9890        .unindent(),
 9891    );
 9892
 9893    // skip-over closing brackets at multiple cursors
 9894    cx.update_editor(|editor, window, cx| {
 9895        editor.handle_input(")", window, cx);
 9896        editor.handle_input("}", window, cx);
 9897    });
 9898    cx.assert_editor_state(
 9899        &"
 9900            )}ˇ
 9901            )}ˇ
 9902            )}ˇ
 9903        "
 9904        .unindent(),
 9905    );
 9906
 9907    // ignore non-close brackets
 9908    cx.update_editor(|editor, window, cx| {
 9909        editor.handle_input("]", window, cx);
 9910        editor.move_left(&MoveLeft, window, cx);
 9911        editor.handle_input("]", window, cx);
 9912    });
 9913    cx.assert_editor_state(
 9914        &"
 9915            )}]ˇ]
 9916            )}]ˇ]
 9917            )}]ˇ]
 9918        "
 9919        .unindent(),
 9920    );
 9921}
 9922
 9923#[gpui::test]
 9924async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
 9925    init_test(cx, |_| {});
 9926
 9927    let mut cx = EditorTestContext::new(cx).await;
 9928
 9929    let html_language = Arc::new(
 9930        Language::new(
 9931            LanguageConfig {
 9932                name: "HTML".into(),
 9933                brackets: BracketPairConfig {
 9934                    pairs: vec![
 9935                        BracketPair {
 9936                            start: "<".into(),
 9937                            end: ">".into(),
 9938                            close: true,
 9939                            ..Default::default()
 9940                        },
 9941                        BracketPair {
 9942                            start: "{".into(),
 9943                            end: "}".into(),
 9944                            close: true,
 9945                            ..Default::default()
 9946                        },
 9947                        BracketPair {
 9948                            start: "(".into(),
 9949                            end: ")".into(),
 9950                            close: true,
 9951                            ..Default::default()
 9952                        },
 9953                    ],
 9954                    ..Default::default()
 9955                },
 9956                autoclose_before: "})]>".into(),
 9957                ..Default::default()
 9958            },
 9959            Some(tree_sitter_html::LANGUAGE.into()),
 9960        )
 9961        .with_injection_query(
 9962            r#"
 9963            (script_element
 9964                (raw_text) @injection.content
 9965                (#set! injection.language "javascript"))
 9966            "#,
 9967        )
 9968        .unwrap(),
 9969    );
 9970
 9971    let javascript_language = Arc::new(Language::new(
 9972        LanguageConfig {
 9973            name: "JavaScript".into(),
 9974            brackets: BracketPairConfig {
 9975                pairs: vec![
 9976                    BracketPair {
 9977                        start: "/*".into(),
 9978                        end: " */".into(),
 9979                        close: true,
 9980                        ..Default::default()
 9981                    },
 9982                    BracketPair {
 9983                        start: "{".into(),
 9984                        end: "}".into(),
 9985                        close: true,
 9986                        ..Default::default()
 9987                    },
 9988                    BracketPair {
 9989                        start: "(".into(),
 9990                        end: ")".into(),
 9991                        close: true,
 9992                        ..Default::default()
 9993                    },
 9994                ],
 9995                ..Default::default()
 9996            },
 9997            autoclose_before: "})]>".into(),
 9998            ..Default::default()
 9999        },
10000        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10001    ));
10002
10003    cx.language_registry().add(html_language.clone());
10004    cx.language_registry().add(javascript_language);
10005    cx.executor().run_until_parked();
10006
10007    cx.update_buffer(|buffer, cx| {
10008        buffer.set_language(Some(html_language), cx);
10009    });
10010
10011    cx.set_state(
10012        &r#"
10013            <body>ˇ
10014                <script>
10015                    var x = 1;ˇ
10016                </script>
10017            </body>ˇ
10018        "#
10019        .unindent(),
10020    );
10021
10022    // Precondition: different languages are active at different locations.
10023    cx.update_editor(|editor, window, cx| {
10024        let snapshot = editor.snapshot(window, cx);
10025        let cursors = editor.selections.ranges::<usize>(cx);
10026        let languages = cursors
10027            .iter()
10028            .map(|c| snapshot.language_at(c.start).unwrap().name())
10029            .collect::<Vec<_>>();
10030        assert_eq!(
10031            languages,
10032            &["HTML".into(), "JavaScript".into(), "HTML".into()]
10033        );
10034    });
10035
10036    // Angle brackets autoclose in HTML, but not JavaScript.
10037    cx.update_editor(|editor, window, cx| {
10038        editor.handle_input("<", window, cx);
10039        editor.handle_input("a", window, cx);
10040    });
10041    cx.assert_editor_state(
10042        &r#"
10043            <body><aˇ>
10044                <script>
10045                    var x = 1;<aˇ
10046                </script>
10047            </body><aˇ>
10048        "#
10049        .unindent(),
10050    );
10051
10052    // Curly braces and parens autoclose in both HTML and JavaScript.
10053    cx.update_editor(|editor, window, cx| {
10054        editor.handle_input(" b=", window, cx);
10055        editor.handle_input("{", window, cx);
10056        editor.handle_input("c", window, cx);
10057        editor.handle_input("(", window, cx);
10058    });
10059    cx.assert_editor_state(
10060        &r#"
10061            <body><a b={c(ˇ)}>
10062                <script>
10063                    var x = 1;<a b={c(ˇ)}
10064                </script>
10065            </body><a b={c(ˇ)}>
10066        "#
10067        .unindent(),
10068    );
10069
10070    // Brackets that were already autoclosed are skipped.
10071    cx.update_editor(|editor, window, cx| {
10072        editor.handle_input(")", window, cx);
10073        editor.handle_input("d", window, cx);
10074        editor.handle_input("}", window, cx);
10075    });
10076    cx.assert_editor_state(
10077        &r#"
10078            <body><a b={c()d}ˇ>
10079                <script>
10080                    var x = 1;<a b={c()d}ˇ
10081                </script>
10082            </body><a b={c()d}ˇ>
10083        "#
10084        .unindent(),
10085    );
10086    cx.update_editor(|editor, window, cx| {
10087        editor.handle_input(">", window, cx);
10088    });
10089    cx.assert_editor_state(
10090        &r#"
10091            <body><a b={c()d}>ˇ
10092                <script>
10093                    var x = 1;<a b={c()d}>ˇ
10094                </script>
10095            </body><a b={c()d}>ˇ
10096        "#
10097        .unindent(),
10098    );
10099
10100    // Reset
10101    cx.set_state(
10102        &r#"
10103            <body>ˇ
10104                <script>
10105                    var x = 1;ˇ
10106                </script>
10107            </body>ˇ
10108        "#
10109        .unindent(),
10110    );
10111
10112    cx.update_editor(|editor, window, cx| {
10113        editor.handle_input("<", window, cx);
10114    });
10115    cx.assert_editor_state(
10116        &r#"
10117            <body><ˇ>
10118                <script>
10119                    var x = 1;<ˇ
10120                </script>
10121            </body><ˇ>
10122        "#
10123        .unindent(),
10124    );
10125
10126    // When backspacing, the closing angle brackets are removed.
10127    cx.update_editor(|editor, window, cx| {
10128        editor.backspace(&Backspace, window, cx);
10129    });
10130    cx.assert_editor_state(
10131        &r#"
10132            <body>ˇ
10133                <script>
10134                    var x = 1;ˇ
10135                </script>
10136            </body>ˇ
10137        "#
10138        .unindent(),
10139    );
10140
10141    // Block comments autoclose in JavaScript, but not HTML.
10142    cx.update_editor(|editor, window, cx| {
10143        editor.handle_input("/", window, cx);
10144        editor.handle_input("*", window, cx);
10145    });
10146    cx.assert_editor_state(
10147        &r#"
10148            <body>/*ˇ
10149                <script>
10150                    var x = 1;/*ˇ */
10151                </script>
10152            </body>/*ˇ
10153        "#
10154        .unindent(),
10155    );
10156}
10157
10158#[gpui::test]
10159async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
10160    init_test(cx, |_| {});
10161
10162    let mut cx = EditorTestContext::new(cx).await;
10163
10164    let rust_language = Arc::new(
10165        Language::new(
10166            LanguageConfig {
10167                name: "Rust".into(),
10168                brackets: serde_json::from_value(json!([
10169                    { "start": "{", "end": "}", "close": true, "newline": true },
10170                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
10171                ]))
10172                .unwrap(),
10173                autoclose_before: "})]>".into(),
10174                ..Default::default()
10175            },
10176            Some(tree_sitter_rust::LANGUAGE.into()),
10177        )
10178        .with_override_query("(string_literal) @string")
10179        .unwrap(),
10180    );
10181
10182    cx.language_registry().add(rust_language.clone());
10183    cx.update_buffer(|buffer, cx| {
10184        buffer.set_language(Some(rust_language), cx);
10185    });
10186
10187    cx.set_state(
10188        &r#"
10189            let x = ˇ
10190        "#
10191        .unindent(),
10192    );
10193
10194    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
10195    cx.update_editor(|editor, window, cx| {
10196        editor.handle_input("\"", window, cx);
10197    });
10198    cx.assert_editor_state(
10199        &r#"
10200            let x = "ˇ"
10201        "#
10202        .unindent(),
10203    );
10204
10205    // Inserting another quotation mark. The cursor moves across the existing
10206    // automatically-inserted quotation mark.
10207    cx.update_editor(|editor, window, cx| {
10208        editor.handle_input("\"", window, cx);
10209    });
10210    cx.assert_editor_state(
10211        &r#"
10212            let x = ""ˇ
10213        "#
10214        .unindent(),
10215    );
10216
10217    // Reset
10218    cx.set_state(
10219        &r#"
10220            let x = ˇ
10221        "#
10222        .unindent(),
10223    );
10224
10225    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
10226    cx.update_editor(|editor, window, cx| {
10227        editor.handle_input("\"", window, cx);
10228        editor.handle_input(" ", window, cx);
10229        editor.move_left(&Default::default(), window, cx);
10230        editor.handle_input("\\", window, cx);
10231        editor.handle_input("\"", window, cx);
10232    });
10233    cx.assert_editor_state(
10234        &r#"
10235            let x = "\"ˇ "
10236        "#
10237        .unindent(),
10238    );
10239
10240    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
10241    // mark. Nothing is inserted.
10242    cx.update_editor(|editor, window, cx| {
10243        editor.move_right(&Default::default(), window, cx);
10244        editor.handle_input("\"", window, cx);
10245    });
10246    cx.assert_editor_state(
10247        &r#"
10248            let x = "\" "ˇ
10249        "#
10250        .unindent(),
10251    );
10252}
10253
10254#[gpui::test]
10255async fn test_surround_with_pair(cx: &mut TestAppContext) {
10256    init_test(cx, |_| {});
10257
10258    let language = Arc::new(Language::new(
10259        LanguageConfig {
10260            brackets: BracketPairConfig {
10261                pairs: vec![
10262                    BracketPair {
10263                        start: "{".to_string(),
10264                        end: "}".to_string(),
10265                        close: true,
10266                        surround: true,
10267                        newline: true,
10268                    },
10269                    BracketPair {
10270                        start: "/* ".to_string(),
10271                        end: "*/".to_string(),
10272                        close: true,
10273                        surround: true,
10274                        ..Default::default()
10275                    },
10276                ],
10277                ..Default::default()
10278            },
10279            ..Default::default()
10280        },
10281        Some(tree_sitter_rust::LANGUAGE.into()),
10282    ));
10283
10284    let text = r#"
10285        a
10286        b
10287        c
10288    "#
10289    .unindent();
10290
10291    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10292    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10293    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10294    editor
10295        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10296        .await;
10297
10298    editor.update_in(cx, |editor, window, cx| {
10299        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10300            s.select_display_ranges([
10301                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10302                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10303                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
10304            ])
10305        });
10306
10307        editor.handle_input("{", window, cx);
10308        editor.handle_input("{", window, cx);
10309        editor.handle_input("{", window, cx);
10310        assert_eq!(
10311            editor.text(cx),
10312            "
10313                {{{a}}}
10314                {{{b}}}
10315                {{{c}}}
10316            "
10317            .unindent()
10318        );
10319        assert_eq!(
10320            editor.selections.display_ranges(cx),
10321            [
10322                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
10323                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
10324                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
10325            ]
10326        );
10327
10328        editor.undo(&Undo, window, cx);
10329        editor.undo(&Undo, window, cx);
10330        editor.undo(&Undo, window, cx);
10331        assert_eq!(
10332            editor.text(cx),
10333            "
10334                a
10335                b
10336                c
10337            "
10338            .unindent()
10339        );
10340        assert_eq!(
10341            editor.selections.display_ranges(cx),
10342            [
10343                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10344                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10345                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10346            ]
10347        );
10348
10349        // Ensure inserting the first character of a multi-byte bracket pair
10350        // doesn't surround the selections with the bracket.
10351        editor.handle_input("/", window, cx);
10352        assert_eq!(
10353            editor.text(cx),
10354            "
10355                /
10356                /
10357                /
10358            "
10359            .unindent()
10360        );
10361        assert_eq!(
10362            editor.selections.display_ranges(cx),
10363            [
10364                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10365                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10366                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10367            ]
10368        );
10369
10370        editor.undo(&Undo, window, cx);
10371        assert_eq!(
10372            editor.text(cx),
10373            "
10374                a
10375                b
10376                c
10377            "
10378            .unindent()
10379        );
10380        assert_eq!(
10381            editor.selections.display_ranges(cx),
10382            [
10383                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10384                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10385                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10386            ]
10387        );
10388
10389        // Ensure inserting the last character of a multi-byte bracket pair
10390        // doesn't surround the selections with the bracket.
10391        editor.handle_input("*", window, cx);
10392        assert_eq!(
10393            editor.text(cx),
10394            "
10395                *
10396                *
10397                *
10398            "
10399            .unindent()
10400        );
10401        assert_eq!(
10402            editor.selections.display_ranges(cx),
10403            [
10404                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10405                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10406                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10407            ]
10408        );
10409    });
10410}
10411
10412#[gpui::test]
10413async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
10414    init_test(cx, |_| {});
10415
10416    let language = Arc::new(Language::new(
10417        LanguageConfig {
10418            brackets: BracketPairConfig {
10419                pairs: vec![BracketPair {
10420                    start: "{".to_string(),
10421                    end: "}".to_string(),
10422                    close: true,
10423                    surround: true,
10424                    newline: true,
10425                }],
10426                ..Default::default()
10427            },
10428            autoclose_before: "}".to_string(),
10429            ..Default::default()
10430        },
10431        Some(tree_sitter_rust::LANGUAGE.into()),
10432    ));
10433
10434    let text = r#"
10435        a
10436        b
10437        c
10438    "#
10439    .unindent();
10440
10441    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10442    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10443    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10444    editor
10445        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10446        .await;
10447
10448    editor.update_in(cx, |editor, window, cx| {
10449        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10450            s.select_ranges([
10451                Point::new(0, 1)..Point::new(0, 1),
10452                Point::new(1, 1)..Point::new(1, 1),
10453                Point::new(2, 1)..Point::new(2, 1),
10454            ])
10455        });
10456
10457        editor.handle_input("{", window, cx);
10458        editor.handle_input("{", window, cx);
10459        editor.handle_input("_", window, cx);
10460        assert_eq!(
10461            editor.text(cx),
10462            "
10463                a{{_}}
10464                b{{_}}
10465                c{{_}}
10466            "
10467            .unindent()
10468        );
10469        assert_eq!(
10470            editor.selections.ranges::<Point>(cx),
10471            [
10472                Point::new(0, 4)..Point::new(0, 4),
10473                Point::new(1, 4)..Point::new(1, 4),
10474                Point::new(2, 4)..Point::new(2, 4)
10475            ]
10476        );
10477
10478        editor.backspace(&Default::default(), window, cx);
10479        editor.backspace(&Default::default(), window, cx);
10480        assert_eq!(
10481            editor.text(cx),
10482            "
10483                a{}
10484                b{}
10485                c{}
10486            "
10487            .unindent()
10488        );
10489        assert_eq!(
10490            editor.selections.ranges::<Point>(cx),
10491            [
10492                Point::new(0, 2)..Point::new(0, 2),
10493                Point::new(1, 2)..Point::new(1, 2),
10494                Point::new(2, 2)..Point::new(2, 2)
10495            ]
10496        );
10497
10498        editor.delete_to_previous_word_start(&Default::default(), window, cx);
10499        assert_eq!(
10500            editor.text(cx),
10501            "
10502                a
10503                b
10504                c
10505            "
10506            .unindent()
10507        );
10508        assert_eq!(
10509            editor.selections.ranges::<Point>(cx),
10510            [
10511                Point::new(0, 1)..Point::new(0, 1),
10512                Point::new(1, 1)..Point::new(1, 1),
10513                Point::new(2, 1)..Point::new(2, 1)
10514            ]
10515        );
10516    });
10517}
10518
10519#[gpui::test]
10520async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
10521    init_test(cx, |settings| {
10522        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
10523    });
10524
10525    let mut cx = EditorTestContext::new(cx).await;
10526
10527    let language = Arc::new(Language::new(
10528        LanguageConfig {
10529            brackets: BracketPairConfig {
10530                pairs: vec![
10531                    BracketPair {
10532                        start: "{".to_string(),
10533                        end: "}".to_string(),
10534                        close: true,
10535                        surround: true,
10536                        newline: true,
10537                    },
10538                    BracketPair {
10539                        start: "(".to_string(),
10540                        end: ")".to_string(),
10541                        close: true,
10542                        surround: true,
10543                        newline: true,
10544                    },
10545                    BracketPair {
10546                        start: "[".to_string(),
10547                        end: "]".to_string(),
10548                        close: false,
10549                        surround: true,
10550                        newline: true,
10551                    },
10552                ],
10553                ..Default::default()
10554            },
10555            autoclose_before: "})]".to_string(),
10556            ..Default::default()
10557        },
10558        Some(tree_sitter_rust::LANGUAGE.into()),
10559    ));
10560
10561    cx.language_registry().add(language.clone());
10562    cx.update_buffer(|buffer, cx| {
10563        buffer.set_language(Some(language), cx);
10564    });
10565
10566    cx.set_state(
10567        &"
10568            {(ˇ)}
10569            [[ˇ]]
10570            {(ˇ)}
10571        "
10572        .unindent(),
10573    );
10574
10575    cx.update_editor(|editor, window, cx| {
10576        editor.backspace(&Default::default(), window, cx);
10577        editor.backspace(&Default::default(), window, cx);
10578    });
10579
10580    cx.assert_editor_state(
10581        &"
10582            ˇ
10583            ˇ]]
10584            ˇ
10585        "
10586        .unindent(),
10587    );
10588
10589    cx.update_editor(|editor, window, cx| {
10590        editor.handle_input("{", window, cx);
10591        editor.handle_input("{", window, cx);
10592        editor.move_right(&MoveRight, window, cx);
10593        editor.move_right(&MoveRight, window, cx);
10594        editor.move_left(&MoveLeft, window, cx);
10595        editor.move_left(&MoveLeft, window, cx);
10596        editor.backspace(&Default::default(), window, cx);
10597    });
10598
10599    cx.assert_editor_state(
10600        &"
10601            {ˇ}
10602            {ˇ}]]
10603            {ˇ}
10604        "
10605        .unindent(),
10606    );
10607
10608    cx.update_editor(|editor, window, cx| {
10609        editor.backspace(&Default::default(), window, cx);
10610    });
10611
10612    cx.assert_editor_state(
10613        &"
10614            ˇ
10615            ˇ]]
10616            ˇ
10617        "
10618        .unindent(),
10619    );
10620}
10621
10622#[gpui::test]
10623async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
10624    init_test(cx, |_| {});
10625
10626    let language = Arc::new(Language::new(
10627        LanguageConfig::default(),
10628        Some(tree_sitter_rust::LANGUAGE.into()),
10629    ));
10630
10631    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
10632    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10633    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10634    editor
10635        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10636        .await;
10637
10638    editor.update_in(cx, |editor, window, cx| {
10639        editor.set_auto_replace_emoji_shortcode(true);
10640
10641        editor.handle_input("Hello ", window, cx);
10642        editor.handle_input(":wave", window, cx);
10643        assert_eq!(editor.text(cx), "Hello :wave".unindent());
10644
10645        editor.handle_input(":", window, cx);
10646        assert_eq!(editor.text(cx), "Hello 👋".unindent());
10647
10648        editor.handle_input(" :smile", window, cx);
10649        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
10650
10651        editor.handle_input(":", window, cx);
10652        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
10653
10654        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
10655        editor.handle_input(":wave", window, cx);
10656        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
10657
10658        editor.handle_input(":", window, cx);
10659        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
10660
10661        editor.handle_input(":1", window, cx);
10662        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
10663
10664        editor.handle_input(":", window, cx);
10665        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
10666
10667        // Ensure shortcode does not get replaced when it is part of a word
10668        editor.handle_input(" Test:wave", window, cx);
10669        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
10670
10671        editor.handle_input(":", window, cx);
10672        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
10673
10674        editor.set_auto_replace_emoji_shortcode(false);
10675
10676        // Ensure shortcode does not get replaced when auto replace is off
10677        editor.handle_input(" :wave", window, cx);
10678        assert_eq!(
10679            editor.text(cx),
10680            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
10681        );
10682
10683        editor.handle_input(":", window, cx);
10684        assert_eq!(
10685            editor.text(cx),
10686            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
10687        );
10688    });
10689}
10690
10691#[gpui::test]
10692async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
10693    init_test(cx, |_| {});
10694
10695    let (text, insertion_ranges) = marked_text_ranges(
10696        indoc! {"
10697            ˇ
10698        "},
10699        false,
10700    );
10701
10702    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
10703    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10704
10705    _ = editor.update_in(cx, |editor, window, cx| {
10706        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
10707
10708        editor
10709            .insert_snippet(&insertion_ranges, snippet, window, cx)
10710            .unwrap();
10711
10712        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
10713            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
10714            assert_eq!(editor.text(cx), expected_text);
10715            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
10716        }
10717
10718        assert(
10719            editor,
10720            cx,
10721            indoc! {"
10722            type «» =•
10723            "},
10724        );
10725
10726        assert!(editor.context_menu_visible(), "There should be a matches");
10727    });
10728}
10729
10730#[gpui::test]
10731async fn test_snippets(cx: &mut TestAppContext) {
10732    init_test(cx, |_| {});
10733
10734    let mut cx = EditorTestContext::new(cx).await;
10735
10736    cx.set_state(indoc! {"
10737        a.ˇ b
10738        a.ˇ b
10739        a.ˇ b
10740    "});
10741
10742    cx.update_editor(|editor, window, cx| {
10743        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
10744        let insertion_ranges = editor
10745            .selections
10746            .all(cx)
10747            .iter()
10748            .map(|s| s.range())
10749            .collect::<Vec<_>>();
10750        editor
10751            .insert_snippet(&insertion_ranges, snippet, window, cx)
10752            .unwrap();
10753    });
10754
10755    cx.assert_editor_state(indoc! {"
10756        a.f(«oneˇ», two, «threeˇ») b
10757        a.f(«oneˇ», two, «threeˇ») b
10758        a.f(«oneˇ», two, «threeˇ») b
10759    "});
10760
10761    // Can't move earlier than the first tab stop
10762    cx.update_editor(|editor, window, cx| {
10763        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
10764    });
10765    cx.assert_editor_state(indoc! {"
10766        a.f(«oneˇ», two, «threeˇ») b
10767        a.f(«oneˇ», two, «threeˇ») b
10768        a.f(«oneˇ», two, «threeˇ») b
10769    "});
10770
10771    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10772    cx.assert_editor_state(indoc! {"
10773        a.f(one, «twoˇ», three) b
10774        a.f(one, «twoˇ», three) b
10775        a.f(one, «twoˇ», three) b
10776    "});
10777
10778    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
10779    cx.assert_editor_state(indoc! {"
10780        a.f(«oneˇ», two, «threeˇ») b
10781        a.f(«oneˇ», two, «threeˇ») b
10782        a.f(«oneˇ», two, «threeˇ») b
10783    "});
10784
10785    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10786    cx.assert_editor_state(indoc! {"
10787        a.f(one, «twoˇ», three) b
10788        a.f(one, «twoˇ», three) b
10789        a.f(one, «twoˇ», three) b
10790    "});
10791    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10792    cx.assert_editor_state(indoc! {"
10793        a.f(one, two, three)ˇ b
10794        a.f(one, two, three)ˇ b
10795        a.f(one, two, three)ˇ b
10796    "});
10797
10798    // As soon as the last tab stop is reached, snippet state is gone
10799    cx.update_editor(|editor, window, cx| {
10800        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
10801    });
10802    cx.assert_editor_state(indoc! {"
10803        a.f(one, two, three)ˇ b
10804        a.f(one, two, three)ˇ b
10805        a.f(one, two, three)ˇ b
10806    "});
10807}
10808
10809#[gpui::test]
10810async fn test_snippet_indentation(cx: &mut TestAppContext) {
10811    init_test(cx, |_| {});
10812
10813    let mut cx = EditorTestContext::new(cx).await;
10814
10815    cx.update_editor(|editor, window, cx| {
10816        let snippet = Snippet::parse(indoc! {"
10817            /*
10818             * Multiline comment with leading indentation
10819             *
10820             * $1
10821             */
10822            $0"})
10823        .unwrap();
10824        let insertion_ranges = editor
10825            .selections
10826            .all(cx)
10827            .iter()
10828            .map(|s| s.range())
10829            .collect::<Vec<_>>();
10830        editor
10831            .insert_snippet(&insertion_ranges, snippet, window, cx)
10832            .unwrap();
10833    });
10834
10835    cx.assert_editor_state(indoc! {"
10836        /*
10837         * Multiline comment with leading indentation
10838         *
10839         * ˇ
10840         */
10841    "});
10842
10843    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10844    cx.assert_editor_state(indoc! {"
10845        /*
10846         * Multiline comment with leading indentation
10847         *
10848         *•
10849         */
10850        ˇ"});
10851}
10852
10853#[gpui::test]
10854async fn test_document_format_during_save(cx: &mut TestAppContext) {
10855    init_test(cx, |_| {});
10856
10857    let fs = FakeFs::new(cx.executor());
10858    fs.insert_file(path!("/file.rs"), Default::default()).await;
10859
10860    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
10861
10862    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10863    language_registry.add(rust_lang());
10864    let mut fake_servers = language_registry.register_fake_lsp(
10865        "Rust",
10866        FakeLspAdapter {
10867            capabilities: lsp::ServerCapabilities {
10868                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10869                ..Default::default()
10870            },
10871            ..Default::default()
10872        },
10873    );
10874
10875    let buffer = project
10876        .update(cx, |project, cx| {
10877            project.open_local_buffer(path!("/file.rs"), cx)
10878        })
10879        .await
10880        .unwrap();
10881
10882    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10883    let (editor, cx) = cx.add_window_view(|window, cx| {
10884        build_editor_with_project(project.clone(), buffer, window, cx)
10885    });
10886    editor.update_in(cx, |editor, window, cx| {
10887        editor.set_text("one\ntwo\nthree\n", window, cx)
10888    });
10889    assert!(cx.read(|cx| editor.is_dirty(cx)));
10890
10891    cx.executor().start_waiting();
10892    let fake_server = fake_servers.next().await.unwrap();
10893
10894    {
10895        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10896            move |params, _| async move {
10897                assert_eq!(
10898                    params.text_document.uri,
10899                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
10900                );
10901                assert_eq!(params.options.tab_size, 4);
10902                Ok(Some(vec![lsp::TextEdit::new(
10903                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10904                    ", ".to_string(),
10905                )]))
10906            },
10907        );
10908        let save = editor
10909            .update_in(cx, |editor, window, cx| {
10910                editor.save(
10911                    SaveOptions {
10912                        format: true,
10913                        autosave: false,
10914                    },
10915                    project.clone(),
10916                    window,
10917                    cx,
10918                )
10919            })
10920            .unwrap();
10921        cx.executor().start_waiting();
10922        save.await;
10923
10924        assert_eq!(
10925            editor.update(cx, |editor, cx| editor.text(cx)),
10926            "one, two\nthree\n"
10927        );
10928        assert!(!cx.read(|cx| editor.is_dirty(cx)));
10929    }
10930
10931    {
10932        editor.update_in(cx, |editor, window, cx| {
10933            editor.set_text("one\ntwo\nthree\n", window, cx)
10934        });
10935        assert!(cx.read(|cx| editor.is_dirty(cx)));
10936
10937        // Ensure we can still save even if formatting hangs.
10938        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10939            move |params, _| async move {
10940                assert_eq!(
10941                    params.text_document.uri,
10942                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
10943                );
10944                futures::future::pending::<()>().await;
10945                unreachable!()
10946            },
10947        );
10948        let save = editor
10949            .update_in(cx, |editor, window, cx| {
10950                editor.save(
10951                    SaveOptions {
10952                        format: true,
10953                        autosave: false,
10954                    },
10955                    project.clone(),
10956                    window,
10957                    cx,
10958                )
10959            })
10960            .unwrap();
10961        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
10962        cx.executor().start_waiting();
10963        save.await;
10964        assert_eq!(
10965            editor.update(cx, |editor, cx| editor.text(cx)),
10966            "one\ntwo\nthree\n"
10967        );
10968    }
10969
10970    // Set rust language override and assert overridden tabsize is sent to language server
10971    update_test_language_settings(cx, |settings| {
10972        settings.languages.0.insert(
10973            "Rust".into(),
10974            LanguageSettingsContent {
10975                tab_size: NonZeroU32::new(8),
10976                ..Default::default()
10977            },
10978        );
10979    });
10980
10981    {
10982        editor.update_in(cx, |editor, window, cx| {
10983            editor.set_text("somehting_new\n", window, cx)
10984        });
10985        assert!(cx.read(|cx| editor.is_dirty(cx)));
10986        let _formatting_request_signal = fake_server
10987            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
10988                assert_eq!(
10989                    params.text_document.uri,
10990                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
10991                );
10992                assert_eq!(params.options.tab_size, 8);
10993                Ok(Some(vec![]))
10994            });
10995        let save = editor
10996            .update_in(cx, |editor, window, cx| {
10997                editor.save(
10998                    SaveOptions {
10999                        format: true,
11000                        autosave: false,
11001                    },
11002                    project.clone(),
11003                    window,
11004                    cx,
11005                )
11006            })
11007            .unwrap();
11008        cx.executor().start_waiting();
11009        save.await;
11010    }
11011}
11012
11013#[gpui::test]
11014async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
11015    init_test(cx, |settings| {
11016        settings.defaults.ensure_final_newline_on_save = Some(false);
11017    });
11018
11019    let fs = FakeFs::new(cx.executor());
11020    fs.insert_file(path!("/file.txt"), "foo".into()).await;
11021
11022    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
11023
11024    let buffer = project
11025        .update(cx, |project, cx| {
11026            project.open_local_buffer(path!("/file.txt"), cx)
11027        })
11028        .await
11029        .unwrap();
11030
11031    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11032    let (editor, cx) = cx.add_window_view(|window, cx| {
11033        build_editor_with_project(project.clone(), buffer, window, cx)
11034    });
11035    editor.update_in(cx, |editor, window, cx| {
11036        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
11037            s.select_ranges([0..0])
11038        });
11039    });
11040    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11041
11042    editor.update_in(cx, |editor, window, cx| {
11043        editor.handle_input("\n", window, cx)
11044    });
11045    cx.run_until_parked();
11046    save(&editor, &project, cx).await;
11047    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11048
11049    editor.update_in(cx, |editor, window, cx| {
11050        editor.undo(&Default::default(), window, cx);
11051    });
11052    save(&editor, &project, cx).await;
11053    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11054
11055    editor.update_in(cx, |editor, window, cx| {
11056        editor.redo(&Default::default(), window, cx);
11057    });
11058    cx.run_until_parked();
11059    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11060
11061    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
11062        let save = editor
11063            .update_in(cx, |editor, window, cx| {
11064                editor.save(
11065                    SaveOptions {
11066                        format: true,
11067                        autosave: false,
11068                    },
11069                    project.clone(),
11070                    window,
11071                    cx,
11072                )
11073            })
11074            .unwrap();
11075        cx.executor().start_waiting();
11076        save.await;
11077        assert!(!cx.read(|cx| editor.is_dirty(cx)));
11078    }
11079}
11080
11081#[gpui::test]
11082async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
11083    init_test(cx, |_| {});
11084
11085    let cols = 4;
11086    let rows = 10;
11087    let sample_text_1 = sample_text(rows, cols, 'a');
11088    assert_eq!(
11089        sample_text_1,
11090        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11091    );
11092    let sample_text_2 = sample_text(rows, cols, 'l');
11093    assert_eq!(
11094        sample_text_2,
11095        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11096    );
11097    let sample_text_3 = sample_text(rows, cols, 'v');
11098    assert_eq!(
11099        sample_text_3,
11100        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11101    );
11102
11103    let fs = FakeFs::new(cx.executor());
11104    fs.insert_tree(
11105        path!("/a"),
11106        json!({
11107            "main.rs": sample_text_1,
11108            "other.rs": sample_text_2,
11109            "lib.rs": sample_text_3,
11110        }),
11111    )
11112    .await;
11113
11114    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11115    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11116    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11117
11118    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11119    language_registry.add(rust_lang());
11120    let mut fake_servers = language_registry.register_fake_lsp(
11121        "Rust",
11122        FakeLspAdapter {
11123            capabilities: lsp::ServerCapabilities {
11124                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11125                ..Default::default()
11126            },
11127            ..Default::default()
11128        },
11129    );
11130
11131    let worktree = project.update(cx, |project, cx| {
11132        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
11133        assert_eq!(worktrees.len(), 1);
11134        worktrees.pop().unwrap()
11135    });
11136    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11137
11138    let buffer_1 = project
11139        .update(cx, |project, cx| {
11140            project.open_buffer((worktree_id, "main.rs"), cx)
11141        })
11142        .await
11143        .unwrap();
11144    let buffer_2 = project
11145        .update(cx, |project, cx| {
11146            project.open_buffer((worktree_id, "other.rs"), cx)
11147        })
11148        .await
11149        .unwrap();
11150    let buffer_3 = project
11151        .update(cx, |project, cx| {
11152            project.open_buffer((worktree_id, "lib.rs"), cx)
11153        })
11154        .await
11155        .unwrap();
11156
11157    let multi_buffer = cx.new(|cx| {
11158        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11159        multi_buffer.push_excerpts(
11160            buffer_1.clone(),
11161            [
11162                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11163                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11164                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11165            ],
11166            cx,
11167        );
11168        multi_buffer.push_excerpts(
11169            buffer_2.clone(),
11170            [
11171                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11172                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11173                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11174            ],
11175            cx,
11176        );
11177        multi_buffer.push_excerpts(
11178            buffer_3.clone(),
11179            [
11180                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11181                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11182                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11183            ],
11184            cx,
11185        );
11186        multi_buffer
11187    });
11188    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
11189        Editor::new(
11190            EditorMode::full(),
11191            multi_buffer,
11192            Some(project.clone()),
11193            window,
11194            cx,
11195        )
11196    });
11197
11198    multi_buffer_editor.update_in(cx, |editor, window, cx| {
11199        editor.change_selections(
11200            SelectionEffects::scroll(Autoscroll::Next),
11201            window,
11202            cx,
11203            |s| s.select_ranges(Some(1..2)),
11204        );
11205        editor.insert("|one|two|three|", window, cx);
11206    });
11207    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
11208    multi_buffer_editor.update_in(cx, |editor, window, cx| {
11209        editor.change_selections(
11210            SelectionEffects::scroll(Autoscroll::Next),
11211            window,
11212            cx,
11213            |s| s.select_ranges(Some(60..70)),
11214        );
11215        editor.insert("|four|five|six|", window, cx);
11216    });
11217    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
11218
11219    // First two buffers should be edited, but not the third one.
11220    assert_eq!(
11221        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11222        "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}",
11223    );
11224    buffer_1.update(cx, |buffer, _| {
11225        assert!(buffer.is_dirty());
11226        assert_eq!(
11227            buffer.text(),
11228            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
11229        )
11230    });
11231    buffer_2.update(cx, |buffer, _| {
11232        assert!(buffer.is_dirty());
11233        assert_eq!(
11234            buffer.text(),
11235            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
11236        )
11237    });
11238    buffer_3.update(cx, |buffer, _| {
11239        assert!(!buffer.is_dirty());
11240        assert_eq!(buffer.text(), sample_text_3,)
11241    });
11242    cx.executor().run_until_parked();
11243
11244    cx.executor().start_waiting();
11245    let save = multi_buffer_editor
11246        .update_in(cx, |editor, window, cx| {
11247            editor.save(
11248                SaveOptions {
11249                    format: true,
11250                    autosave: false,
11251                },
11252                project.clone(),
11253                window,
11254                cx,
11255            )
11256        })
11257        .unwrap();
11258
11259    let fake_server = fake_servers.next().await.unwrap();
11260    fake_server
11261        .server
11262        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
11263            Ok(Some(vec![lsp::TextEdit::new(
11264                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11265                format!("[{} formatted]", params.text_document.uri),
11266            )]))
11267        })
11268        .detach();
11269    save.await;
11270
11271    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
11272    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
11273    assert_eq!(
11274        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11275        uri!(
11276            "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}"
11277        ),
11278    );
11279    buffer_1.update(cx, |buffer, _| {
11280        assert!(!buffer.is_dirty());
11281        assert_eq!(
11282            buffer.text(),
11283            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
11284        )
11285    });
11286    buffer_2.update(cx, |buffer, _| {
11287        assert!(!buffer.is_dirty());
11288        assert_eq!(
11289            buffer.text(),
11290            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
11291        )
11292    });
11293    buffer_3.update(cx, |buffer, _| {
11294        assert!(!buffer.is_dirty());
11295        assert_eq!(buffer.text(), sample_text_3,)
11296    });
11297}
11298
11299#[gpui::test]
11300async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
11301    init_test(cx, |_| {});
11302
11303    let fs = FakeFs::new(cx.executor());
11304    fs.insert_tree(
11305        path!("/dir"),
11306        json!({
11307            "file1.rs": "fn main() { println!(\"hello\"); }",
11308            "file2.rs": "fn test() { println!(\"test\"); }",
11309            "file3.rs": "fn other() { println!(\"other\"); }\n",
11310        }),
11311    )
11312    .await;
11313
11314    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
11315    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11316    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11317
11318    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11319    language_registry.add(rust_lang());
11320
11321    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
11322    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11323
11324    // Open three buffers
11325    let buffer_1 = project
11326        .update(cx, |project, cx| {
11327            project.open_buffer((worktree_id, "file1.rs"), cx)
11328        })
11329        .await
11330        .unwrap();
11331    let buffer_2 = project
11332        .update(cx, |project, cx| {
11333            project.open_buffer((worktree_id, "file2.rs"), cx)
11334        })
11335        .await
11336        .unwrap();
11337    let buffer_3 = project
11338        .update(cx, |project, cx| {
11339            project.open_buffer((worktree_id, "file3.rs"), cx)
11340        })
11341        .await
11342        .unwrap();
11343
11344    // Create a multi-buffer with all three buffers
11345    let multi_buffer = cx.new(|cx| {
11346        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11347        multi_buffer.push_excerpts(
11348            buffer_1.clone(),
11349            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11350            cx,
11351        );
11352        multi_buffer.push_excerpts(
11353            buffer_2.clone(),
11354            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11355            cx,
11356        );
11357        multi_buffer.push_excerpts(
11358            buffer_3.clone(),
11359            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11360            cx,
11361        );
11362        multi_buffer
11363    });
11364
11365    let editor = cx.new_window_entity(|window, cx| {
11366        Editor::new(
11367            EditorMode::full(),
11368            multi_buffer,
11369            Some(project.clone()),
11370            window,
11371            cx,
11372        )
11373    });
11374
11375    // Edit only the first buffer
11376    editor.update_in(cx, |editor, window, cx| {
11377        editor.change_selections(
11378            SelectionEffects::scroll(Autoscroll::Next),
11379            window,
11380            cx,
11381            |s| s.select_ranges(Some(10..10)),
11382        );
11383        editor.insert("// edited", window, cx);
11384    });
11385
11386    // Verify that only buffer 1 is dirty
11387    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
11388    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11389    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11390
11391    // Get write counts after file creation (files were created with initial content)
11392    // We expect each file to have been written once during creation
11393    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
11394    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
11395    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
11396
11397    // Perform autosave
11398    let save_task = editor.update_in(cx, |editor, window, cx| {
11399        editor.save(
11400            SaveOptions {
11401                format: true,
11402                autosave: true,
11403            },
11404            project.clone(),
11405            window,
11406            cx,
11407        )
11408    });
11409    save_task.await.unwrap();
11410
11411    // Only the dirty buffer should have been saved
11412    assert_eq!(
11413        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
11414        1,
11415        "Buffer 1 was dirty, so it should have been written once during autosave"
11416    );
11417    assert_eq!(
11418        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
11419        0,
11420        "Buffer 2 was clean, so it should not have been written during autosave"
11421    );
11422    assert_eq!(
11423        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
11424        0,
11425        "Buffer 3 was clean, so it should not have been written during autosave"
11426    );
11427
11428    // Verify buffer states after autosave
11429    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11430    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11431    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11432
11433    // Now perform a manual save (format = true)
11434    let save_task = editor.update_in(cx, |editor, window, cx| {
11435        editor.save(
11436            SaveOptions {
11437                format: true,
11438                autosave: false,
11439            },
11440            project.clone(),
11441            window,
11442            cx,
11443        )
11444    });
11445    save_task.await.unwrap();
11446
11447    // During manual save, clean buffers don't get written to disk
11448    // They just get did_save called for language server notifications
11449    assert_eq!(
11450        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
11451        1,
11452        "Buffer 1 should only have been written once total (during autosave, not manual save)"
11453    );
11454    assert_eq!(
11455        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
11456        0,
11457        "Buffer 2 should not have been written at all"
11458    );
11459    assert_eq!(
11460        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
11461        0,
11462        "Buffer 3 should not have been written at all"
11463    );
11464}
11465
11466async fn setup_range_format_test(
11467    cx: &mut TestAppContext,
11468) -> (
11469    Entity<Project>,
11470    Entity<Editor>,
11471    &mut gpui::VisualTestContext,
11472    lsp::FakeLanguageServer,
11473) {
11474    init_test(cx, |_| {});
11475
11476    let fs = FakeFs::new(cx.executor());
11477    fs.insert_file(path!("/file.rs"), Default::default()).await;
11478
11479    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11480
11481    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11482    language_registry.add(rust_lang());
11483    let mut fake_servers = language_registry.register_fake_lsp(
11484        "Rust",
11485        FakeLspAdapter {
11486            capabilities: lsp::ServerCapabilities {
11487                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
11488                ..lsp::ServerCapabilities::default()
11489            },
11490            ..FakeLspAdapter::default()
11491        },
11492    );
11493
11494    let buffer = project
11495        .update(cx, |project, cx| {
11496            project.open_local_buffer(path!("/file.rs"), cx)
11497        })
11498        .await
11499        .unwrap();
11500
11501    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11502    let (editor, cx) = cx.add_window_view(|window, cx| {
11503        build_editor_with_project(project.clone(), buffer, window, cx)
11504    });
11505
11506    cx.executor().start_waiting();
11507    let fake_server = fake_servers.next().await.unwrap();
11508
11509    (project, editor, cx, fake_server)
11510}
11511
11512#[gpui::test]
11513async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
11514    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11515
11516    editor.update_in(cx, |editor, window, cx| {
11517        editor.set_text("one\ntwo\nthree\n", window, cx)
11518    });
11519    assert!(cx.read(|cx| editor.is_dirty(cx)));
11520
11521    let save = editor
11522        .update_in(cx, |editor, window, cx| {
11523            editor.save(
11524                SaveOptions {
11525                    format: true,
11526                    autosave: false,
11527                },
11528                project.clone(),
11529                window,
11530                cx,
11531            )
11532        })
11533        .unwrap();
11534    fake_server
11535        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
11536            assert_eq!(
11537                params.text_document.uri,
11538                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11539            );
11540            assert_eq!(params.options.tab_size, 4);
11541            Ok(Some(vec![lsp::TextEdit::new(
11542                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11543                ", ".to_string(),
11544            )]))
11545        })
11546        .next()
11547        .await;
11548    cx.executor().start_waiting();
11549    save.await;
11550    assert_eq!(
11551        editor.update(cx, |editor, cx| editor.text(cx)),
11552        "one, two\nthree\n"
11553    );
11554    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11555}
11556
11557#[gpui::test]
11558async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
11559    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11560
11561    editor.update_in(cx, |editor, window, cx| {
11562        editor.set_text("one\ntwo\nthree\n", window, cx)
11563    });
11564    assert!(cx.read(|cx| editor.is_dirty(cx)));
11565
11566    // Test that save still works when formatting hangs
11567    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
11568        move |params, _| async move {
11569            assert_eq!(
11570                params.text_document.uri,
11571                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11572            );
11573            futures::future::pending::<()>().await;
11574            unreachable!()
11575        },
11576    );
11577    let save = editor
11578        .update_in(cx, |editor, window, cx| {
11579            editor.save(
11580                SaveOptions {
11581                    format: true,
11582                    autosave: false,
11583                },
11584                project.clone(),
11585                window,
11586                cx,
11587            )
11588        })
11589        .unwrap();
11590    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11591    cx.executor().start_waiting();
11592    save.await;
11593    assert_eq!(
11594        editor.update(cx, |editor, cx| editor.text(cx)),
11595        "one\ntwo\nthree\n"
11596    );
11597    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11598}
11599
11600#[gpui::test]
11601async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
11602    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11603
11604    // Buffer starts clean, no formatting should be requested
11605    let save = editor
11606        .update_in(cx, |editor, window, cx| {
11607            editor.save(
11608                SaveOptions {
11609                    format: false,
11610                    autosave: false,
11611                },
11612                project.clone(),
11613                window,
11614                cx,
11615            )
11616        })
11617        .unwrap();
11618    let _pending_format_request = fake_server
11619        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
11620            panic!("Should not be invoked");
11621        })
11622        .next();
11623    cx.executor().start_waiting();
11624    save.await;
11625    cx.run_until_parked();
11626}
11627
11628#[gpui::test]
11629async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
11630    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11631
11632    // Set Rust language override and assert overridden tabsize is sent to language server
11633    update_test_language_settings(cx, |settings| {
11634        settings.languages.0.insert(
11635            "Rust".into(),
11636            LanguageSettingsContent {
11637                tab_size: NonZeroU32::new(8),
11638                ..Default::default()
11639            },
11640        );
11641    });
11642
11643    editor.update_in(cx, |editor, window, cx| {
11644        editor.set_text("something_new\n", window, cx)
11645    });
11646    assert!(cx.read(|cx| editor.is_dirty(cx)));
11647    let save = editor
11648        .update_in(cx, |editor, window, cx| {
11649            editor.save(
11650                SaveOptions {
11651                    format: true,
11652                    autosave: false,
11653                },
11654                project.clone(),
11655                window,
11656                cx,
11657            )
11658        })
11659        .unwrap();
11660    fake_server
11661        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
11662            assert_eq!(
11663                params.text_document.uri,
11664                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11665            );
11666            assert_eq!(params.options.tab_size, 8);
11667            Ok(Some(Vec::new()))
11668        })
11669        .next()
11670        .await;
11671    save.await;
11672}
11673
11674#[gpui::test]
11675async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
11676    init_test(cx, |settings| {
11677        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
11678            Formatter::LanguageServer { name: None },
11679        )))
11680    });
11681
11682    let fs = FakeFs::new(cx.executor());
11683    fs.insert_file(path!("/file.rs"), Default::default()).await;
11684
11685    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11686
11687    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11688    language_registry.add(Arc::new(Language::new(
11689        LanguageConfig {
11690            name: "Rust".into(),
11691            matcher: LanguageMatcher {
11692                path_suffixes: vec!["rs".to_string()],
11693                ..Default::default()
11694            },
11695            ..LanguageConfig::default()
11696        },
11697        Some(tree_sitter_rust::LANGUAGE.into()),
11698    )));
11699    update_test_language_settings(cx, |settings| {
11700        // Enable Prettier formatting for the same buffer, and ensure
11701        // LSP is called instead of Prettier.
11702        settings.defaults.prettier = Some(PrettierSettings {
11703            allowed: true,
11704            ..PrettierSettings::default()
11705        });
11706    });
11707    let mut fake_servers = language_registry.register_fake_lsp(
11708        "Rust",
11709        FakeLspAdapter {
11710            capabilities: lsp::ServerCapabilities {
11711                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11712                ..Default::default()
11713            },
11714            ..Default::default()
11715        },
11716    );
11717
11718    let buffer = project
11719        .update(cx, |project, cx| {
11720            project.open_local_buffer(path!("/file.rs"), cx)
11721        })
11722        .await
11723        .unwrap();
11724
11725    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11726    let (editor, cx) = cx.add_window_view(|window, cx| {
11727        build_editor_with_project(project.clone(), buffer, window, cx)
11728    });
11729    editor.update_in(cx, |editor, window, cx| {
11730        editor.set_text("one\ntwo\nthree\n", window, cx)
11731    });
11732
11733    cx.executor().start_waiting();
11734    let fake_server = fake_servers.next().await.unwrap();
11735
11736    let format = editor
11737        .update_in(cx, |editor, window, cx| {
11738            editor.perform_format(
11739                project.clone(),
11740                FormatTrigger::Manual,
11741                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
11742                window,
11743                cx,
11744            )
11745        })
11746        .unwrap();
11747    fake_server
11748        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
11749            assert_eq!(
11750                params.text_document.uri,
11751                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11752            );
11753            assert_eq!(params.options.tab_size, 4);
11754            Ok(Some(vec![lsp::TextEdit::new(
11755                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11756                ", ".to_string(),
11757            )]))
11758        })
11759        .next()
11760        .await;
11761    cx.executor().start_waiting();
11762    format.await;
11763    assert_eq!(
11764        editor.update(cx, |editor, cx| editor.text(cx)),
11765        "one, two\nthree\n"
11766    );
11767
11768    editor.update_in(cx, |editor, window, cx| {
11769        editor.set_text("one\ntwo\nthree\n", window, cx)
11770    });
11771    // Ensure we don't lock if formatting hangs.
11772    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11773        move |params, _| async move {
11774            assert_eq!(
11775                params.text_document.uri,
11776                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11777            );
11778            futures::future::pending::<()>().await;
11779            unreachable!()
11780        },
11781    );
11782    let format = editor
11783        .update_in(cx, |editor, window, cx| {
11784            editor.perform_format(
11785                project,
11786                FormatTrigger::Manual,
11787                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
11788                window,
11789                cx,
11790            )
11791        })
11792        .unwrap();
11793    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11794    cx.executor().start_waiting();
11795    format.await;
11796    assert_eq!(
11797        editor.update(cx, |editor, cx| editor.text(cx)),
11798        "one\ntwo\nthree\n"
11799    );
11800}
11801
11802#[gpui::test]
11803async fn test_multiple_formatters(cx: &mut TestAppContext) {
11804    init_test(cx, |settings| {
11805        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
11806        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
11807            Formatter::LanguageServer { name: None },
11808            Formatter::CodeActions(
11809                [
11810                    ("code-action-1".into(), true),
11811                    ("code-action-2".into(), true),
11812                ]
11813                .into_iter()
11814                .collect(),
11815            ),
11816        ])))
11817    });
11818
11819    let fs = FakeFs::new(cx.executor());
11820    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
11821        .await;
11822
11823    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11824    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11825    language_registry.add(rust_lang());
11826
11827    let mut fake_servers = language_registry.register_fake_lsp(
11828        "Rust",
11829        FakeLspAdapter {
11830            capabilities: lsp::ServerCapabilities {
11831                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11832                execute_command_provider: Some(lsp::ExecuteCommandOptions {
11833                    commands: vec!["the-command-for-code-action-1".into()],
11834                    ..Default::default()
11835                }),
11836                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
11837                ..Default::default()
11838            },
11839            ..Default::default()
11840        },
11841    );
11842
11843    let buffer = project
11844        .update(cx, |project, cx| {
11845            project.open_local_buffer(path!("/file.rs"), cx)
11846        })
11847        .await
11848        .unwrap();
11849
11850    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11851    let (editor, cx) = cx.add_window_view(|window, cx| {
11852        build_editor_with_project(project.clone(), buffer, window, cx)
11853    });
11854
11855    cx.executor().start_waiting();
11856
11857    let fake_server = fake_servers.next().await.unwrap();
11858    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11859        move |_params, _| async move {
11860            Ok(Some(vec![lsp::TextEdit::new(
11861                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
11862                "applied-formatting\n".to_string(),
11863            )]))
11864        },
11865    );
11866    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
11867        move |params, _| async move {
11868            assert_eq!(
11869                params.context.only,
11870                Some(vec!["code-action-1".into(), "code-action-2".into()])
11871            );
11872            let uri = lsp::Uri::from_file_path(path!("/file.rs")).unwrap();
11873            Ok(Some(vec![
11874                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
11875                    kind: Some("code-action-1".into()),
11876                    edit: Some(lsp::WorkspaceEdit::new(
11877                        [(
11878                            uri.clone(),
11879                            vec![lsp::TextEdit::new(
11880                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
11881                                "applied-code-action-1-edit\n".to_string(),
11882                            )],
11883                        )]
11884                        .into_iter()
11885                        .collect(),
11886                    )),
11887                    command: Some(lsp::Command {
11888                        command: "the-command-for-code-action-1".into(),
11889                        ..Default::default()
11890                    }),
11891                    ..Default::default()
11892                }),
11893                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
11894                    kind: Some("code-action-2".into()),
11895                    edit: Some(lsp::WorkspaceEdit::new(
11896                        [(
11897                            uri,
11898                            vec![lsp::TextEdit::new(
11899                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
11900                                "applied-code-action-2-edit\n".to_string(),
11901                            )],
11902                        )]
11903                        .into_iter()
11904                        .collect(),
11905                    )),
11906                    ..Default::default()
11907                }),
11908            ]))
11909        },
11910    );
11911
11912    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
11913        move |params, _| async move { Ok(params) }
11914    });
11915
11916    let command_lock = Arc::new(futures::lock::Mutex::new(()));
11917    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
11918        let fake = fake_server.clone();
11919        let lock = command_lock.clone();
11920        move |params, _| {
11921            assert_eq!(params.command, "the-command-for-code-action-1");
11922            let fake = fake.clone();
11923            let lock = lock.clone();
11924            async move {
11925                lock.lock().await;
11926                fake.server
11927                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
11928                        label: None,
11929                        edit: lsp::WorkspaceEdit {
11930                            changes: Some(
11931                                [(
11932                                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
11933                                    vec![lsp::TextEdit {
11934                                        range: lsp::Range::new(
11935                                            lsp::Position::new(0, 0),
11936                                            lsp::Position::new(0, 0),
11937                                        ),
11938                                        new_text: "applied-code-action-1-command\n".into(),
11939                                    }],
11940                                )]
11941                                .into_iter()
11942                                .collect(),
11943                            ),
11944                            ..Default::default()
11945                        },
11946                    })
11947                    .await
11948                    .into_response()
11949                    .unwrap();
11950                Ok(Some(json!(null)))
11951            }
11952        }
11953    });
11954
11955    cx.executor().start_waiting();
11956    editor
11957        .update_in(cx, |editor, window, cx| {
11958            editor.perform_format(
11959                project.clone(),
11960                FormatTrigger::Manual,
11961                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
11962                window,
11963                cx,
11964            )
11965        })
11966        .unwrap()
11967        .await;
11968    editor.update(cx, |editor, cx| {
11969        assert_eq!(
11970            editor.text(cx),
11971            r#"
11972                applied-code-action-2-edit
11973                applied-code-action-1-command
11974                applied-code-action-1-edit
11975                applied-formatting
11976                one
11977                two
11978                three
11979            "#
11980            .unindent()
11981        );
11982    });
11983
11984    editor.update_in(cx, |editor, window, cx| {
11985        editor.undo(&Default::default(), window, cx);
11986        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
11987    });
11988
11989    // Perform a manual edit while waiting for an LSP command
11990    // that's being run as part of a formatting code action.
11991    let lock_guard = command_lock.lock().await;
11992    let format = editor
11993        .update_in(cx, |editor, window, cx| {
11994            editor.perform_format(
11995                project.clone(),
11996                FormatTrigger::Manual,
11997                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
11998                window,
11999                cx,
12000            )
12001        })
12002        .unwrap();
12003    cx.run_until_parked();
12004    editor.update(cx, |editor, cx| {
12005        assert_eq!(
12006            editor.text(cx),
12007            r#"
12008                applied-code-action-1-edit
12009                applied-formatting
12010                one
12011                two
12012                three
12013            "#
12014            .unindent()
12015        );
12016
12017        editor.buffer.update(cx, |buffer, cx| {
12018            let ix = buffer.len(cx);
12019            buffer.edit([(ix..ix, "edited\n")], None, cx);
12020        });
12021    });
12022
12023    // Allow the LSP command to proceed. Because the buffer was edited,
12024    // the second code action will not be run.
12025    drop(lock_guard);
12026    format.await;
12027    editor.update_in(cx, |editor, window, cx| {
12028        assert_eq!(
12029            editor.text(cx),
12030            r#"
12031                applied-code-action-1-command
12032                applied-code-action-1-edit
12033                applied-formatting
12034                one
12035                two
12036                three
12037                edited
12038            "#
12039            .unindent()
12040        );
12041
12042        // The manual edit is undone first, because it is the last thing the user did
12043        // (even though the command completed afterwards).
12044        editor.undo(&Default::default(), window, cx);
12045        assert_eq!(
12046            editor.text(cx),
12047            r#"
12048                applied-code-action-1-command
12049                applied-code-action-1-edit
12050                applied-formatting
12051                one
12052                two
12053                three
12054            "#
12055            .unindent()
12056        );
12057
12058        // All the formatting (including the command, which completed after the manual edit)
12059        // is undone together.
12060        editor.undo(&Default::default(), window, cx);
12061        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12062    });
12063}
12064
12065#[gpui::test]
12066async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
12067    init_test(cx, |settings| {
12068        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
12069            Formatter::LanguageServer { name: None },
12070        ])))
12071    });
12072
12073    let fs = FakeFs::new(cx.executor());
12074    fs.insert_file(path!("/file.ts"), Default::default()).await;
12075
12076    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12077
12078    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12079    language_registry.add(Arc::new(Language::new(
12080        LanguageConfig {
12081            name: "TypeScript".into(),
12082            matcher: LanguageMatcher {
12083                path_suffixes: vec!["ts".to_string()],
12084                ..Default::default()
12085            },
12086            ..LanguageConfig::default()
12087        },
12088        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12089    )));
12090    update_test_language_settings(cx, |settings| {
12091        settings.defaults.prettier = Some(PrettierSettings {
12092            allowed: true,
12093            ..PrettierSettings::default()
12094        });
12095    });
12096    let mut fake_servers = language_registry.register_fake_lsp(
12097        "TypeScript",
12098        FakeLspAdapter {
12099            capabilities: lsp::ServerCapabilities {
12100                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
12101                ..Default::default()
12102            },
12103            ..Default::default()
12104        },
12105    );
12106
12107    let buffer = project
12108        .update(cx, |project, cx| {
12109            project.open_local_buffer(path!("/file.ts"), cx)
12110        })
12111        .await
12112        .unwrap();
12113
12114    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12115    let (editor, cx) = cx.add_window_view(|window, cx| {
12116        build_editor_with_project(project.clone(), buffer, window, cx)
12117    });
12118    editor.update_in(cx, |editor, window, cx| {
12119        editor.set_text(
12120            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12121            window,
12122            cx,
12123        )
12124    });
12125
12126    cx.executor().start_waiting();
12127    let fake_server = fake_servers.next().await.unwrap();
12128
12129    let format = editor
12130        .update_in(cx, |editor, window, cx| {
12131            editor.perform_code_action_kind(
12132                project.clone(),
12133                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12134                window,
12135                cx,
12136            )
12137        })
12138        .unwrap();
12139    fake_server
12140        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
12141            assert_eq!(
12142                params.text_document.uri,
12143                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12144            );
12145            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
12146                lsp::CodeAction {
12147                    title: "Organize Imports".to_string(),
12148                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
12149                    edit: Some(lsp::WorkspaceEdit {
12150                        changes: Some(
12151                            [(
12152                                params.text_document.uri.clone(),
12153                                vec![lsp::TextEdit::new(
12154                                    lsp::Range::new(
12155                                        lsp::Position::new(1, 0),
12156                                        lsp::Position::new(2, 0),
12157                                    ),
12158                                    "".to_string(),
12159                                )],
12160                            )]
12161                            .into_iter()
12162                            .collect(),
12163                        ),
12164                        ..Default::default()
12165                    }),
12166                    ..Default::default()
12167                },
12168            )]))
12169        })
12170        .next()
12171        .await;
12172    cx.executor().start_waiting();
12173    format.await;
12174    assert_eq!(
12175        editor.update(cx, |editor, cx| editor.text(cx)),
12176        "import { a } from 'module';\n\nconst x = a;\n"
12177    );
12178
12179    editor.update_in(cx, |editor, window, cx| {
12180        editor.set_text(
12181            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12182            window,
12183            cx,
12184        )
12185    });
12186    // Ensure we don't lock if code action hangs.
12187    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
12188        move |params, _| async move {
12189            assert_eq!(
12190                params.text_document.uri,
12191                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12192            );
12193            futures::future::pending::<()>().await;
12194            unreachable!()
12195        },
12196    );
12197    let format = editor
12198        .update_in(cx, |editor, window, cx| {
12199            editor.perform_code_action_kind(
12200                project,
12201                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12202                window,
12203                cx,
12204            )
12205        })
12206        .unwrap();
12207    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
12208    cx.executor().start_waiting();
12209    format.await;
12210    assert_eq!(
12211        editor.update(cx, |editor, cx| editor.text(cx)),
12212        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
12213    );
12214}
12215
12216#[gpui::test]
12217async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
12218    init_test(cx, |_| {});
12219
12220    let mut cx = EditorLspTestContext::new_rust(
12221        lsp::ServerCapabilities {
12222            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12223            ..Default::default()
12224        },
12225        cx,
12226    )
12227    .await;
12228
12229    cx.set_state(indoc! {"
12230        one.twoˇ
12231    "});
12232
12233    // The format request takes a long time. When it completes, it inserts
12234    // a newline and an indent before the `.`
12235    cx.lsp
12236        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
12237            let executor = cx.background_executor().clone();
12238            async move {
12239                executor.timer(Duration::from_millis(100)).await;
12240                Ok(Some(vec![lsp::TextEdit {
12241                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
12242                    new_text: "\n    ".into(),
12243                }]))
12244            }
12245        });
12246
12247    // Submit a format request.
12248    let format_1 = cx
12249        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12250        .unwrap();
12251    cx.executor().run_until_parked();
12252
12253    // Submit a second format request.
12254    let format_2 = cx
12255        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12256        .unwrap();
12257    cx.executor().run_until_parked();
12258
12259    // Wait for both format requests to complete
12260    cx.executor().advance_clock(Duration::from_millis(200));
12261    cx.executor().start_waiting();
12262    format_1.await.unwrap();
12263    cx.executor().start_waiting();
12264    format_2.await.unwrap();
12265
12266    // The formatting edits only happens once.
12267    cx.assert_editor_state(indoc! {"
12268        one
12269            .twoˇ
12270    "});
12271}
12272
12273#[gpui::test]
12274async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
12275    init_test(cx, |settings| {
12276        settings.defaults.formatter = Some(SelectedFormatter::Auto)
12277    });
12278
12279    let mut cx = EditorLspTestContext::new_rust(
12280        lsp::ServerCapabilities {
12281            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12282            ..Default::default()
12283        },
12284        cx,
12285    )
12286    .await;
12287
12288    // Set up a buffer white some trailing whitespace and no trailing newline.
12289    cx.set_state(
12290        &[
12291            "one ",   //
12292            "twoˇ",   //
12293            "three ", //
12294            "four",   //
12295        ]
12296        .join("\n"),
12297    );
12298
12299    // Submit a format request.
12300    let format = cx
12301        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12302        .unwrap();
12303
12304    // Record which buffer changes have been sent to the language server
12305    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
12306    cx.lsp
12307        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
12308            let buffer_changes = buffer_changes.clone();
12309            move |params, _| {
12310                buffer_changes.lock().extend(
12311                    params
12312                        .content_changes
12313                        .into_iter()
12314                        .map(|e| (e.range.unwrap(), e.text)),
12315                );
12316            }
12317        });
12318
12319    // Handle formatting requests to the language server.
12320    cx.lsp
12321        .set_request_handler::<lsp::request::Formatting, _, _>({
12322            let buffer_changes = buffer_changes.clone();
12323            move |_, _| {
12324                // When formatting is requested, trailing whitespace has already been stripped,
12325                // and the trailing newline has already been added.
12326                assert_eq!(
12327                    &buffer_changes.lock()[1..],
12328                    &[
12329                        (
12330                            lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
12331                            "".into()
12332                        ),
12333                        (
12334                            lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
12335                            "".into()
12336                        ),
12337                        (
12338                            lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
12339                            "\n".into()
12340                        ),
12341                    ]
12342                );
12343
12344                // Insert blank lines between each line of the buffer.
12345                async move {
12346                    Ok(Some(vec![
12347                        lsp::TextEdit {
12348                            range: lsp::Range::new(
12349                                lsp::Position::new(1, 0),
12350                                lsp::Position::new(1, 0),
12351                            ),
12352                            new_text: "\n".into(),
12353                        },
12354                        lsp::TextEdit {
12355                            range: lsp::Range::new(
12356                                lsp::Position::new(2, 0),
12357                                lsp::Position::new(2, 0),
12358                            ),
12359                            new_text: "\n".into(),
12360                        },
12361                    ]))
12362                }
12363            }
12364        });
12365
12366    // After formatting the buffer, the trailing whitespace is stripped,
12367    // a newline is appended, and the edits provided by the language server
12368    // have been applied.
12369    format.await.unwrap();
12370    cx.assert_editor_state(
12371        &[
12372            "one",   //
12373            "",      //
12374            "twoˇ",  //
12375            "",      //
12376            "three", //
12377            "four",  //
12378            "",      //
12379        ]
12380        .join("\n"),
12381    );
12382
12383    // Undoing the formatting undoes the trailing whitespace removal, the
12384    // trailing newline, and the LSP edits.
12385    cx.update_buffer(|buffer, cx| buffer.undo(cx));
12386    cx.assert_editor_state(
12387        &[
12388            "one ",   //
12389            "twoˇ",   //
12390            "three ", //
12391            "four",   //
12392        ]
12393        .join("\n"),
12394    );
12395}
12396
12397#[gpui::test]
12398async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
12399    cx: &mut TestAppContext,
12400) {
12401    init_test(cx, |_| {});
12402
12403    cx.update(|cx| {
12404        cx.update_global::<SettingsStore, _>(|settings, cx| {
12405            settings.update_user_settings::<EditorSettings>(cx, |settings| {
12406                settings.auto_signature_help = Some(true);
12407            });
12408        });
12409    });
12410
12411    let mut cx = EditorLspTestContext::new_rust(
12412        lsp::ServerCapabilities {
12413            signature_help_provider: Some(lsp::SignatureHelpOptions {
12414                ..Default::default()
12415            }),
12416            ..Default::default()
12417        },
12418        cx,
12419    )
12420    .await;
12421
12422    let language = Language::new(
12423        LanguageConfig {
12424            name: "Rust".into(),
12425            brackets: BracketPairConfig {
12426                pairs: vec![
12427                    BracketPair {
12428                        start: "{".to_string(),
12429                        end: "}".to_string(),
12430                        close: true,
12431                        surround: true,
12432                        newline: true,
12433                    },
12434                    BracketPair {
12435                        start: "(".to_string(),
12436                        end: ")".to_string(),
12437                        close: true,
12438                        surround: true,
12439                        newline: true,
12440                    },
12441                    BracketPair {
12442                        start: "/*".to_string(),
12443                        end: " */".to_string(),
12444                        close: true,
12445                        surround: true,
12446                        newline: true,
12447                    },
12448                    BracketPair {
12449                        start: "[".to_string(),
12450                        end: "]".to_string(),
12451                        close: false,
12452                        surround: false,
12453                        newline: true,
12454                    },
12455                    BracketPair {
12456                        start: "\"".to_string(),
12457                        end: "\"".to_string(),
12458                        close: true,
12459                        surround: true,
12460                        newline: false,
12461                    },
12462                    BracketPair {
12463                        start: "<".to_string(),
12464                        end: ">".to_string(),
12465                        close: false,
12466                        surround: true,
12467                        newline: true,
12468                    },
12469                ],
12470                ..Default::default()
12471            },
12472            autoclose_before: "})]".to_string(),
12473            ..Default::default()
12474        },
12475        Some(tree_sitter_rust::LANGUAGE.into()),
12476    );
12477    let language = Arc::new(language);
12478
12479    cx.language_registry().add(language.clone());
12480    cx.update_buffer(|buffer, cx| {
12481        buffer.set_language(Some(language), cx);
12482    });
12483
12484    cx.set_state(
12485        &r#"
12486            fn main() {
12487                sampleˇ
12488            }
12489        "#
12490        .unindent(),
12491    );
12492
12493    cx.update_editor(|editor, window, cx| {
12494        editor.handle_input("(", window, cx);
12495    });
12496    cx.assert_editor_state(
12497        &"
12498            fn main() {
12499                sample(ˇ)
12500            }
12501        "
12502        .unindent(),
12503    );
12504
12505    let mocked_response = lsp::SignatureHelp {
12506        signatures: vec![lsp::SignatureInformation {
12507            label: "fn sample(param1: u8, param2: u8)".to_string(),
12508            documentation: None,
12509            parameters: Some(vec![
12510                lsp::ParameterInformation {
12511                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12512                    documentation: None,
12513                },
12514                lsp::ParameterInformation {
12515                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12516                    documentation: None,
12517                },
12518            ]),
12519            active_parameter: None,
12520        }],
12521        active_signature: Some(0),
12522        active_parameter: Some(0),
12523    };
12524    handle_signature_help_request(&mut cx, mocked_response).await;
12525
12526    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12527        .await;
12528
12529    cx.editor(|editor, _, _| {
12530        let signature_help_state = editor.signature_help_state.popover().cloned();
12531        let signature = signature_help_state.unwrap();
12532        assert_eq!(
12533            signature.signatures[signature.current_signature].label,
12534            "fn sample(param1: u8, param2: u8)"
12535        );
12536    });
12537}
12538
12539#[gpui::test]
12540async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
12541    init_test(cx, |_| {});
12542
12543    cx.update(|cx| {
12544        cx.update_global::<SettingsStore, _>(|settings, cx| {
12545            settings.update_user_settings::<EditorSettings>(cx, |settings| {
12546                settings.auto_signature_help = Some(false);
12547                settings.show_signature_help_after_edits = Some(false);
12548            });
12549        });
12550    });
12551
12552    let mut cx = EditorLspTestContext::new_rust(
12553        lsp::ServerCapabilities {
12554            signature_help_provider: Some(lsp::SignatureHelpOptions {
12555                ..Default::default()
12556            }),
12557            ..Default::default()
12558        },
12559        cx,
12560    )
12561    .await;
12562
12563    let language = Language::new(
12564        LanguageConfig {
12565            name: "Rust".into(),
12566            brackets: BracketPairConfig {
12567                pairs: vec![
12568                    BracketPair {
12569                        start: "{".to_string(),
12570                        end: "}".to_string(),
12571                        close: true,
12572                        surround: true,
12573                        newline: true,
12574                    },
12575                    BracketPair {
12576                        start: "(".to_string(),
12577                        end: ")".to_string(),
12578                        close: true,
12579                        surround: true,
12580                        newline: true,
12581                    },
12582                    BracketPair {
12583                        start: "/*".to_string(),
12584                        end: " */".to_string(),
12585                        close: true,
12586                        surround: true,
12587                        newline: true,
12588                    },
12589                    BracketPair {
12590                        start: "[".to_string(),
12591                        end: "]".to_string(),
12592                        close: false,
12593                        surround: false,
12594                        newline: true,
12595                    },
12596                    BracketPair {
12597                        start: "\"".to_string(),
12598                        end: "\"".to_string(),
12599                        close: true,
12600                        surround: true,
12601                        newline: false,
12602                    },
12603                    BracketPair {
12604                        start: "<".to_string(),
12605                        end: ">".to_string(),
12606                        close: false,
12607                        surround: true,
12608                        newline: true,
12609                    },
12610                ],
12611                ..Default::default()
12612            },
12613            autoclose_before: "})]".to_string(),
12614            ..Default::default()
12615        },
12616        Some(tree_sitter_rust::LANGUAGE.into()),
12617    );
12618    let language = Arc::new(language);
12619
12620    cx.language_registry().add(language.clone());
12621    cx.update_buffer(|buffer, cx| {
12622        buffer.set_language(Some(language), cx);
12623    });
12624
12625    // Ensure that signature_help is not called when no signature help is enabled.
12626    cx.set_state(
12627        &r#"
12628            fn main() {
12629                sampleˇ
12630            }
12631        "#
12632        .unindent(),
12633    );
12634    cx.update_editor(|editor, window, cx| {
12635        editor.handle_input("(", window, cx);
12636    });
12637    cx.assert_editor_state(
12638        &"
12639            fn main() {
12640                sample(ˇ)
12641            }
12642        "
12643        .unindent(),
12644    );
12645    cx.editor(|editor, _, _| {
12646        assert!(editor.signature_help_state.task().is_none());
12647    });
12648
12649    let mocked_response = lsp::SignatureHelp {
12650        signatures: vec![lsp::SignatureInformation {
12651            label: "fn sample(param1: u8, param2: u8)".to_string(),
12652            documentation: None,
12653            parameters: Some(vec![
12654                lsp::ParameterInformation {
12655                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12656                    documentation: None,
12657                },
12658                lsp::ParameterInformation {
12659                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12660                    documentation: None,
12661                },
12662            ]),
12663            active_parameter: None,
12664        }],
12665        active_signature: Some(0),
12666        active_parameter: Some(0),
12667    };
12668
12669    // Ensure that signature_help is called when enabled afte edits
12670    cx.update(|_, cx| {
12671        cx.update_global::<SettingsStore, _>(|settings, cx| {
12672            settings.update_user_settings::<EditorSettings>(cx, |settings| {
12673                settings.auto_signature_help = Some(false);
12674                settings.show_signature_help_after_edits = Some(true);
12675            });
12676        });
12677    });
12678    cx.set_state(
12679        &r#"
12680            fn main() {
12681                sampleˇ
12682            }
12683        "#
12684        .unindent(),
12685    );
12686    cx.update_editor(|editor, window, cx| {
12687        editor.handle_input("(", window, cx);
12688    });
12689    cx.assert_editor_state(
12690        &"
12691            fn main() {
12692                sample(ˇ)
12693            }
12694        "
12695        .unindent(),
12696    );
12697    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12698    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12699        .await;
12700    cx.update_editor(|editor, _, _| {
12701        let signature_help_state = editor.signature_help_state.popover().cloned();
12702        assert!(signature_help_state.is_some());
12703        let signature = signature_help_state.unwrap();
12704        assert_eq!(
12705            signature.signatures[signature.current_signature].label,
12706            "fn sample(param1: u8, param2: u8)"
12707        );
12708        editor.signature_help_state = SignatureHelpState::default();
12709    });
12710
12711    // Ensure that signature_help is called when auto signature help override is enabled
12712    cx.update(|_, cx| {
12713        cx.update_global::<SettingsStore, _>(|settings, cx| {
12714            settings.update_user_settings::<EditorSettings>(cx, |settings| {
12715                settings.auto_signature_help = Some(true);
12716                settings.show_signature_help_after_edits = Some(false);
12717            });
12718        });
12719    });
12720    cx.set_state(
12721        &r#"
12722            fn main() {
12723                sampleˇ
12724            }
12725        "#
12726        .unindent(),
12727    );
12728    cx.update_editor(|editor, window, cx| {
12729        editor.handle_input("(", window, cx);
12730    });
12731    cx.assert_editor_state(
12732        &"
12733            fn main() {
12734                sample(ˇ)
12735            }
12736        "
12737        .unindent(),
12738    );
12739    handle_signature_help_request(&mut cx, mocked_response).await;
12740    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12741        .await;
12742    cx.editor(|editor, _, _| {
12743        let signature_help_state = editor.signature_help_state.popover().cloned();
12744        assert!(signature_help_state.is_some());
12745        let signature = signature_help_state.unwrap();
12746        assert_eq!(
12747            signature.signatures[signature.current_signature].label,
12748            "fn sample(param1: u8, param2: u8)"
12749        );
12750    });
12751}
12752
12753#[gpui::test]
12754async fn test_signature_help(cx: &mut TestAppContext) {
12755    init_test(cx, |_| {});
12756    cx.update(|cx| {
12757        cx.update_global::<SettingsStore, _>(|settings, cx| {
12758            settings.update_user_settings::<EditorSettings>(cx, |settings| {
12759                settings.auto_signature_help = Some(true);
12760            });
12761        });
12762    });
12763
12764    let mut cx = EditorLspTestContext::new_rust(
12765        lsp::ServerCapabilities {
12766            signature_help_provider: Some(lsp::SignatureHelpOptions {
12767                ..Default::default()
12768            }),
12769            ..Default::default()
12770        },
12771        cx,
12772    )
12773    .await;
12774
12775    // A test that directly calls `show_signature_help`
12776    cx.update_editor(|editor, window, cx| {
12777        editor.show_signature_help(&ShowSignatureHelp, window, cx);
12778    });
12779
12780    let mocked_response = lsp::SignatureHelp {
12781        signatures: vec![lsp::SignatureInformation {
12782            label: "fn sample(param1: u8, param2: u8)".to_string(),
12783            documentation: None,
12784            parameters: Some(vec![
12785                lsp::ParameterInformation {
12786                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12787                    documentation: None,
12788                },
12789                lsp::ParameterInformation {
12790                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12791                    documentation: None,
12792                },
12793            ]),
12794            active_parameter: None,
12795        }],
12796        active_signature: Some(0),
12797        active_parameter: Some(0),
12798    };
12799    handle_signature_help_request(&mut cx, mocked_response).await;
12800
12801    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12802        .await;
12803
12804    cx.editor(|editor, _, _| {
12805        let signature_help_state = editor.signature_help_state.popover().cloned();
12806        assert!(signature_help_state.is_some());
12807        let signature = signature_help_state.unwrap();
12808        assert_eq!(
12809            signature.signatures[signature.current_signature].label,
12810            "fn sample(param1: u8, param2: u8)"
12811        );
12812    });
12813
12814    // When exiting outside from inside the brackets, `signature_help` is closed.
12815    cx.set_state(indoc! {"
12816        fn main() {
12817            sample(ˇ);
12818        }
12819
12820        fn sample(param1: u8, param2: u8) {}
12821    "});
12822
12823    cx.update_editor(|editor, window, cx| {
12824        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12825            s.select_ranges([0..0])
12826        });
12827    });
12828
12829    let mocked_response = lsp::SignatureHelp {
12830        signatures: Vec::new(),
12831        active_signature: None,
12832        active_parameter: None,
12833    };
12834    handle_signature_help_request(&mut cx, mocked_response).await;
12835
12836    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
12837        .await;
12838
12839    cx.editor(|editor, _, _| {
12840        assert!(!editor.signature_help_state.is_shown());
12841    });
12842
12843    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
12844    cx.set_state(indoc! {"
12845        fn main() {
12846            sample(ˇ);
12847        }
12848
12849        fn sample(param1: u8, param2: u8) {}
12850    "});
12851
12852    let mocked_response = lsp::SignatureHelp {
12853        signatures: vec![lsp::SignatureInformation {
12854            label: "fn sample(param1: u8, param2: u8)".to_string(),
12855            documentation: None,
12856            parameters: Some(vec![
12857                lsp::ParameterInformation {
12858                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12859                    documentation: None,
12860                },
12861                lsp::ParameterInformation {
12862                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12863                    documentation: None,
12864                },
12865            ]),
12866            active_parameter: None,
12867        }],
12868        active_signature: Some(0),
12869        active_parameter: Some(0),
12870    };
12871    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12872    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12873        .await;
12874    cx.editor(|editor, _, _| {
12875        assert!(editor.signature_help_state.is_shown());
12876    });
12877
12878    // Restore the popover with more parameter input
12879    cx.set_state(indoc! {"
12880        fn main() {
12881            sample(param1, param2ˇ);
12882        }
12883
12884        fn sample(param1: u8, param2: u8) {}
12885    "});
12886
12887    let mocked_response = lsp::SignatureHelp {
12888        signatures: vec![lsp::SignatureInformation {
12889            label: "fn sample(param1: u8, param2: u8)".to_string(),
12890            documentation: None,
12891            parameters: Some(vec![
12892                lsp::ParameterInformation {
12893                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12894                    documentation: None,
12895                },
12896                lsp::ParameterInformation {
12897                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12898                    documentation: None,
12899                },
12900            ]),
12901            active_parameter: None,
12902        }],
12903        active_signature: Some(0),
12904        active_parameter: Some(1),
12905    };
12906    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12907    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12908        .await;
12909
12910    // When selecting a range, the popover is gone.
12911    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
12912    cx.update_editor(|editor, window, cx| {
12913        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12914            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
12915        })
12916    });
12917    cx.assert_editor_state(indoc! {"
12918        fn main() {
12919            sample(param1, «ˇparam2»);
12920        }
12921
12922        fn sample(param1: u8, param2: u8) {}
12923    "});
12924    cx.editor(|editor, _, _| {
12925        assert!(!editor.signature_help_state.is_shown());
12926    });
12927
12928    // When unselecting again, the popover is back if within the brackets.
12929    cx.update_editor(|editor, window, cx| {
12930        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12931            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
12932        })
12933    });
12934    cx.assert_editor_state(indoc! {"
12935        fn main() {
12936            sample(param1, ˇparam2);
12937        }
12938
12939        fn sample(param1: u8, param2: u8) {}
12940    "});
12941    handle_signature_help_request(&mut cx, mocked_response).await;
12942    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12943        .await;
12944    cx.editor(|editor, _, _| {
12945        assert!(editor.signature_help_state.is_shown());
12946    });
12947
12948    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
12949    cx.update_editor(|editor, window, cx| {
12950        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12951            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
12952            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
12953        })
12954    });
12955    cx.assert_editor_state(indoc! {"
12956        fn main() {
12957            sample(param1, ˇparam2);
12958        }
12959
12960        fn sample(param1: u8, param2: u8) {}
12961    "});
12962
12963    let mocked_response = lsp::SignatureHelp {
12964        signatures: vec![lsp::SignatureInformation {
12965            label: "fn sample(param1: u8, param2: u8)".to_string(),
12966            documentation: None,
12967            parameters: Some(vec![
12968                lsp::ParameterInformation {
12969                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12970                    documentation: None,
12971                },
12972                lsp::ParameterInformation {
12973                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12974                    documentation: None,
12975                },
12976            ]),
12977            active_parameter: None,
12978        }],
12979        active_signature: Some(0),
12980        active_parameter: Some(1),
12981    };
12982    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12983    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12984        .await;
12985    cx.update_editor(|editor, _, cx| {
12986        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
12987    });
12988    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
12989        .await;
12990    cx.update_editor(|editor, window, cx| {
12991        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12992            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
12993        })
12994    });
12995    cx.assert_editor_state(indoc! {"
12996        fn main() {
12997            sample(param1, «ˇparam2»);
12998        }
12999
13000        fn sample(param1: u8, param2: u8) {}
13001    "});
13002    cx.update_editor(|editor, window, cx| {
13003        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13004            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13005        })
13006    });
13007    cx.assert_editor_state(indoc! {"
13008        fn main() {
13009            sample(param1, ˇparam2);
13010        }
13011
13012        fn sample(param1: u8, param2: u8) {}
13013    "});
13014    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
13015        .await;
13016}
13017
13018#[gpui::test]
13019async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
13020    init_test(cx, |_| {});
13021
13022    let mut cx = EditorLspTestContext::new_rust(
13023        lsp::ServerCapabilities {
13024            signature_help_provider: Some(lsp::SignatureHelpOptions {
13025                ..Default::default()
13026            }),
13027            ..Default::default()
13028        },
13029        cx,
13030    )
13031    .await;
13032
13033    cx.set_state(indoc! {"
13034        fn main() {
13035            overloadedˇ
13036        }
13037    "});
13038
13039    cx.update_editor(|editor, window, cx| {
13040        editor.handle_input("(", window, cx);
13041        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13042    });
13043
13044    // Mock response with 3 signatures
13045    let mocked_response = lsp::SignatureHelp {
13046        signatures: vec![
13047            lsp::SignatureInformation {
13048                label: "fn overloaded(x: i32)".to_string(),
13049                documentation: None,
13050                parameters: Some(vec![lsp::ParameterInformation {
13051                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13052                    documentation: None,
13053                }]),
13054                active_parameter: None,
13055            },
13056            lsp::SignatureInformation {
13057                label: "fn overloaded(x: i32, y: i32)".to_string(),
13058                documentation: None,
13059                parameters: Some(vec![
13060                    lsp::ParameterInformation {
13061                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13062                        documentation: None,
13063                    },
13064                    lsp::ParameterInformation {
13065                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13066                        documentation: None,
13067                    },
13068                ]),
13069                active_parameter: None,
13070            },
13071            lsp::SignatureInformation {
13072                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
13073                documentation: None,
13074                parameters: Some(vec![
13075                    lsp::ParameterInformation {
13076                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13077                        documentation: None,
13078                    },
13079                    lsp::ParameterInformation {
13080                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13081                        documentation: None,
13082                    },
13083                    lsp::ParameterInformation {
13084                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
13085                        documentation: None,
13086                    },
13087                ]),
13088                active_parameter: None,
13089            },
13090        ],
13091        active_signature: Some(1),
13092        active_parameter: Some(0),
13093    };
13094    handle_signature_help_request(&mut cx, mocked_response).await;
13095
13096    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13097        .await;
13098
13099    // Verify we have multiple signatures and the right one is selected
13100    cx.editor(|editor, _, _| {
13101        let popover = editor.signature_help_state.popover().cloned().unwrap();
13102        assert_eq!(popover.signatures.len(), 3);
13103        // active_signature was 1, so that should be the current
13104        assert_eq!(popover.current_signature, 1);
13105        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
13106        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
13107        assert_eq!(
13108            popover.signatures[2].label,
13109            "fn overloaded(x: i32, y: i32, z: i32)"
13110        );
13111    });
13112
13113    // Test navigation functionality
13114    cx.update_editor(|editor, window, cx| {
13115        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13116    });
13117
13118    cx.editor(|editor, _, _| {
13119        let popover = editor.signature_help_state.popover().cloned().unwrap();
13120        assert_eq!(popover.current_signature, 2);
13121    });
13122
13123    // Test wrap around
13124    cx.update_editor(|editor, window, cx| {
13125        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13126    });
13127
13128    cx.editor(|editor, _, _| {
13129        let popover = editor.signature_help_state.popover().cloned().unwrap();
13130        assert_eq!(popover.current_signature, 0);
13131    });
13132
13133    // Test previous navigation
13134    cx.update_editor(|editor, window, cx| {
13135        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
13136    });
13137
13138    cx.editor(|editor, _, _| {
13139        let popover = editor.signature_help_state.popover().cloned().unwrap();
13140        assert_eq!(popover.current_signature, 2);
13141    });
13142}
13143
13144#[gpui::test]
13145async fn test_completion_mode(cx: &mut TestAppContext) {
13146    init_test(cx, |_| {});
13147    let mut cx = EditorLspTestContext::new_rust(
13148        lsp::ServerCapabilities {
13149            completion_provider: Some(lsp::CompletionOptions {
13150                resolve_provider: Some(true),
13151                ..Default::default()
13152            }),
13153            ..Default::default()
13154        },
13155        cx,
13156    )
13157    .await;
13158
13159    struct Run {
13160        run_description: &'static str,
13161        initial_state: String,
13162        buffer_marked_text: String,
13163        completion_label: &'static str,
13164        completion_text: &'static str,
13165        expected_with_insert_mode: String,
13166        expected_with_replace_mode: String,
13167        expected_with_replace_subsequence_mode: String,
13168        expected_with_replace_suffix_mode: String,
13169    }
13170
13171    let runs = [
13172        Run {
13173            run_description: "Start of word matches completion text",
13174            initial_state: "before ediˇ after".into(),
13175            buffer_marked_text: "before <edi|> after".into(),
13176            completion_label: "editor",
13177            completion_text: "editor",
13178            expected_with_insert_mode: "before editorˇ after".into(),
13179            expected_with_replace_mode: "before editorˇ after".into(),
13180            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13181            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13182        },
13183        Run {
13184            run_description: "Accept same text at the middle of the word",
13185            initial_state: "before ediˇtor after".into(),
13186            buffer_marked_text: "before <edi|tor> after".into(),
13187            completion_label: "editor",
13188            completion_text: "editor",
13189            expected_with_insert_mode: "before editorˇtor after".into(),
13190            expected_with_replace_mode: "before editorˇ after".into(),
13191            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13192            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13193        },
13194        Run {
13195            run_description: "End of word matches completion text -- cursor at end",
13196            initial_state: "before torˇ after".into(),
13197            buffer_marked_text: "before <tor|> after".into(),
13198            completion_label: "editor",
13199            completion_text: "editor",
13200            expected_with_insert_mode: "before editorˇ after".into(),
13201            expected_with_replace_mode: "before editorˇ after".into(),
13202            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13203            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13204        },
13205        Run {
13206            run_description: "End of word matches completion text -- cursor at start",
13207            initial_state: "before ˇtor after".into(),
13208            buffer_marked_text: "before <|tor> after".into(),
13209            completion_label: "editor",
13210            completion_text: "editor",
13211            expected_with_insert_mode: "before editorˇtor after".into(),
13212            expected_with_replace_mode: "before editorˇ after".into(),
13213            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13214            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13215        },
13216        Run {
13217            run_description: "Prepend text containing whitespace",
13218            initial_state: "pˇfield: bool".into(),
13219            buffer_marked_text: "<p|field>: bool".into(),
13220            completion_label: "pub ",
13221            completion_text: "pub ",
13222            expected_with_insert_mode: "pub ˇfield: bool".into(),
13223            expected_with_replace_mode: "pub ˇ: bool".into(),
13224            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
13225            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
13226        },
13227        Run {
13228            run_description: "Add element to start of list",
13229            initial_state: "[element_ˇelement_2]".into(),
13230            buffer_marked_text: "[<element_|element_2>]".into(),
13231            completion_label: "element_1",
13232            completion_text: "element_1",
13233            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
13234            expected_with_replace_mode: "[element_1ˇ]".into(),
13235            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
13236            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
13237        },
13238        Run {
13239            run_description: "Add element to start of list -- first and second elements are equal",
13240            initial_state: "[elˇelement]".into(),
13241            buffer_marked_text: "[<el|element>]".into(),
13242            completion_label: "element",
13243            completion_text: "element",
13244            expected_with_insert_mode: "[elementˇelement]".into(),
13245            expected_with_replace_mode: "[elementˇ]".into(),
13246            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
13247            expected_with_replace_suffix_mode: "[elementˇ]".into(),
13248        },
13249        Run {
13250            run_description: "Ends with matching suffix",
13251            initial_state: "SubˇError".into(),
13252            buffer_marked_text: "<Sub|Error>".into(),
13253            completion_label: "SubscriptionError",
13254            completion_text: "SubscriptionError",
13255            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
13256            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13257            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13258            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
13259        },
13260        Run {
13261            run_description: "Suffix is a subsequence -- contiguous",
13262            initial_state: "SubˇErr".into(),
13263            buffer_marked_text: "<Sub|Err>".into(),
13264            completion_label: "SubscriptionError",
13265            completion_text: "SubscriptionError",
13266            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
13267            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13268            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13269            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
13270        },
13271        Run {
13272            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
13273            initial_state: "Suˇscrirr".into(),
13274            buffer_marked_text: "<Su|scrirr>".into(),
13275            completion_label: "SubscriptionError",
13276            completion_text: "SubscriptionError",
13277            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
13278            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13279            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13280            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
13281        },
13282        Run {
13283            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
13284            initial_state: "foo(indˇix)".into(),
13285            buffer_marked_text: "foo(<ind|ix>)".into(),
13286            completion_label: "node_index",
13287            completion_text: "node_index",
13288            expected_with_insert_mode: "foo(node_indexˇix)".into(),
13289            expected_with_replace_mode: "foo(node_indexˇ)".into(),
13290            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
13291            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
13292        },
13293        Run {
13294            run_description: "Replace range ends before cursor - should extend to cursor",
13295            initial_state: "before editˇo after".into(),
13296            buffer_marked_text: "before <{ed}>it|o after".into(),
13297            completion_label: "editor",
13298            completion_text: "editor",
13299            expected_with_insert_mode: "before editorˇo after".into(),
13300            expected_with_replace_mode: "before editorˇo after".into(),
13301            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
13302            expected_with_replace_suffix_mode: "before editorˇo after".into(),
13303        },
13304        Run {
13305            run_description: "Uses label for suffix matching",
13306            initial_state: "before ediˇtor after".into(),
13307            buffer_marked_text: "before <edi|tor> after".into(),
13308            completion_label: "editor",
13309            completion_text: "editor()",
13310            expected_with_insert_mode: "before editor()ˇtor after".into(),
13311            expected_with_replace_mode: "before editor()ˇ after".into(),
13312            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
13313            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
13314        },
13315        Run {
13316            run_description: "Case insensitive subsequence and suffix matching",
13317            initial_state: "before EDiˇtoR after".into(),
13318            buffer_marked_text: "before <EDi|toR> after".into(),
13319            completion_label: "editor",
13320            completion_text: "editor",
13321            expected_with_insert_mode: "before editorˇtoR after".into(),
13322            expected_with_replace_mode: "before editorˇ after".into(),
13323            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13324            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13325        },
13326    ];
13327
13328    for run in runs {
13329        let run_variations = [
13330            (LspInsertMode::Insert, run.expected_with_insert_mode),
13331            (LspInsertMode::Replace, run.expected_with_replace_mode),
13332            (
13333                LspInsertMode::ReplaceSubsequence,
13334                run.expected_with_replace_subsequence_mode,
13335            ),
13336            (
13337                LspInsertMode::ReplaceSuffix,
13338                run.expected_with_replace_suffix_mode,
13339            ),
13340        ];
13341
13342        for (lsp_insert_mode, expected_text) in run_variations {
13343            eprintln!(
13344                "run = {:?}, mode = {lsp_insert_mode:.?}",
13345                run.run_description,
13346            );
13347
13348            update_test_language_settings(&mut cx, |settings| {
13349                settings.defaults.completions = Some(CompletionSettings {
13350                    lsp_insert_mode,
13351                    words: WordsCompletionMode::Disabled,
13352                    words_min_length: 0,
13353                    lsp: true,
13354                    lsp_fetch_timeout_ms: 0,
13355                });
13356            });
13357
13358            cx.set_state(&run.initial_state);
13359            cx.update_editor(|editor, window, cx| {
13360                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13361            });
13362
13363            let counter = Arc::new(AtomicUsize::new(0));
13364            handle_completion_request_with_insert_and_replace(
13365                &mut cx,
13366                &run.buffer_marked_text,
13367                vec![(run.completion_label, run.completion_text)],
13368                counter.clone(),
13369            )
13370            .await;
13371            cx.condition(|editor, _| editor.context_menu_visible())
13372                .await;
13373            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13374
13375            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13376                editor
13377                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
13378                    .unwrap()
13379            });
13380            cx.assert_editor_state(&expected_text);
13381            handle_resolve_completion_request(&mut cx, None).await;
13382            apply_additional_edits.await.unwrap();
13383        }
13384    }
13385}
13386
13387#[gpui::test]
13388async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
13389    init_test(cx, |_| {});
13390    let mut cx = EditorLspTestContext::new_rust(
13391        lsp::ServerCapabilities {
13392            completion_provider: Some(lsp::CompletionOptions {
13393                resolve_provider: Some(true),
13394                ..Default::default()
13395            }),
13396            ..Default::default()
13397        },
13398        cx,
13399    )
13400    .await;
13401
13402    let initial_state = "SubˇError";
13403    let buffer_marked_text = "<Sub|Error>";
13404    let completion_text = "SubscriptionError";
13405    let expected_with_insert_mode = "SubscriptionErrorˇError";
13406    let expected_with_replace_mode = "SubscriptionErrorˇ";
13407
13408    update_test_language_settings(&mut cx, |settings| {
13409        settings.defaults.completions = Some(CompletionSettings {
13410            words: WordsCompletionMode::Disabled,
13411            words_min_length: 0,
13412            // set the opposite here to ensure that the action is overriding the default behavior
13413            lsp_insert_mode: LspInsertMode::Insert,
13414            lsp: true,
13415            lsp_fetch_timeout_ms: 0,
13416        });
13417    });
13418
13419    cx.set_state(initial_state);
13420    cx.update_editor(|editor, window, cx| {
13421        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13422    });
13423
13424    let counter = Arc::new(AtomicUsize::new(0));
13425    handle_completion_request_with_insert_and_replace(
13426        &mut cx,
13427        buffer_marked_text,
13428        vec![(completion_text, completion_text)],
13429        counter.clone(),
13430    )
13431    .await;
13432    cx.condition(|editor, _| editor.context_menu_visible())
13433        .await;
13434    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13435
13436    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13437        editor
13438            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13439            .unwrap()
13440    });
13441    cx.assert_editor_state(expected_with_replace_mode);
13442    handle_resolve_completion_request(&mut cx, None).await;
13443    apply_additional_edits.await.unwrap();
13444
13445    update_test_language_settings(&mut cx, |settings| {
13446        settings.defaults.completions = Some(CompletionSettings {
13447            words: WordsCompletionMode::Disabled,
13448            words_min_length: 0,
13449            // set the opposite here to ensure that the action is overriding the default behavior
13450            lsp_insert_mode: LspInsertMode::Replace,
13451            lsp: true,
13452            lsp_fetch_timeout_ms: 0,
13453        });
13454    });
13455
13456    cx.set_state(initial_state);
13457    cx.update_editor(|editor, window, cx| {
13458        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13459    });
13460    handle_completion_request_with_insert_and_replace(
13461        &mut cx,
13462        buffer_marked_text,
13463        vec![(completion_text, completion_text)],
13464        counter.clone(),
13465    )
13466    .await;
13467    cx.condition(|editor, _| editor.context_menu_visible())
13468        .await;
13469    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
13470
13471    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13472        editor
13473            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
13474            .unwrap()
13475    });
13476    cx.assert_editor_state(expected_with_insert_mode);
13477    handle_resolve_completion_request(&mut cx, None).await;
13478    apply_additional_edits.await.unwrap();
13479}
13480
13481#[gpui::test]
13482async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
13483    init_test(cx, |_| {});
13484    let mut cx = EditorLspTestContext::new_rust(
13485        lsp::ServerCapabilities {
13486            completion_provider: Some(lsp::CompletionOptions {
13487                resolve_provider: Some(true),
13488                ..Default::default()
13489            }),
13490            ..Default::default()
13491        },
13492        cx,
13493    )
13494    .await;
13495
13496    // scenario: surrounding text matches completion text
13497    let completion_text = "to_offset";
13498    let initial_state = indoc! {"
13499        1. buf.to_offˇsuffix
13500        2. buf.to_offˇsuf
13501        3. buf.to_offˇfix
13502        4. buf.to_offˇ
13503        5. into_offˇensive
13504        6. ˇsuffix
13505        7. let ˇ //
13506        8. aaˇzz
13507        9. buf.to_off«zzzzzˇ»suffix
13508        10. buf.«ˇzzzzz»suffix
13509        11. to_off«ˇzzzzz»
13510
13511        buf.to_offˇsuffix  // newest cursor
13512    "};
13513    let completion_marked_buffer = indoc! {"
13514        1. buf.to_offsuffix
13515        2. buf.to_offsuf
13516        3. buf.to_offfix
13517        4. buf.to_off
13518        5. into_offensive
13519        6. suffix
13520        7. let  //
13521        8. aazz
13522        9. buf.to_offzzzzzsuffix
13523        10. buf.zzzzzsuffix
13524        11. to_offzzzzz
13525
13526        buf.<to_off|suffix>  // newest cursor
13527    "};
13528    let expected = indoc! {"
13529        1. buf.to_offsetˇ
13530        2. buf.to_offsetˇsuf
13531        3. buf.to_offsetˇfix
13532        4. buf.to_offsetˇ
13533        5. into_offsetˇensive
13534        6. to_offsetˇsuffix
13535        7. let to_offsetˇ //
13536        8. aato_offsetˇzz
13537        9. buf.to_offsetˇ
13538        10. buf.to_offsetˇsuffix
13539        11. to_offsetˇ
13540
13541        buf.to_offsetˇ  // newest cursor
13542    "};
13543    cx.set_state(initial_state);
13544    cx.update_editor(|editor, window, cx| {
13545        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13546    });
13547    handle_completion_request_with_insert_and_replace(
13548        &mut cx,
13549        completion_marked_buffer,
13550        vec![(completion_text, completion_text)],
13551        Arc::new(AtomicUsize::new(0)),
13552    )
13553    .await;
13554    cx.condition(|editor, _| editor.context_menu_visible())
13555        .await;
13556    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13557        editor
13558            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13559            .unwrap()
13560    });
13561    cx.assert_editor_state(expected);
13562    handle_resolve_completion_request(&mut cx, None).await;
13563    apply_additional_edits.await.unwrap();
13564
13565    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
13566    let completion_text = "foo_and_bar";
13567    let initial_state = indoc! {"
13568        1. ooanbˇ
13569        2. zooanbˇ
13570        3. ooanbˇz
13571        4. zooanbˇz
13572        5. ooanˇ
13573        6. oanbˇ
13574
13575        ooanbˇ
13576    "};
13577    let completion_marked_buffer = indoc! {"
13578        1. ooanb
13579        2. zooanb
13580        3. ooanbz
13581        4. zooanbz
13582        5. ooan
13583        6. oanb
13584
13585        <ooanb|>
13586    "};
13587    let expected = indoc! {"
13588        1. foo_and_barˇ
13589        2. zfoo_and_barˇ
13590        3. foo_and_barˇz
13591        4. zfoo_and_barˇz
13592        5. ooanfoo_and_barˇ
13593        6. oanbfoo_and_barˇ
13594
13595        foo_and_barˇ
13596    "};
13597    cx.set_state(initial_state);
13598    cx.update_editor(|editor, window, cx| {
13599        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13600    });
13601    handle_completion_request_with_insert_and_replace(
13602        &mut cx,
13603        completion_marked_buffer,
13604        vec![(completion_text, completion_text)],
13605        Arc::new(AtomicUsize::new(0)),
13606    )
13607    .await;
13608    cx.condition(|editor, _| editor.context_menu_visible())
13609        .await;
13610    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13611        editor
13612            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13613            .unwrap()
13614    });
13615    cx.assert_editor_state(expected);
13616    handle_resolve_completion_request(&mut cx, None).await;
13617    apply_additional_edits.await.unwrap();
13618
13619    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
13620    // (expects the same as if it was inserted at the end)
13621    let completion_text = "foo_and_bar";
13622    let initial_state = indoc! {"
13623        1. ooˇanb
13624        2. zooˇanb
13625        3. ooˇanbz
13626        4. zooˇanbz
13627
13628        ooˇanb
13629    "};
13630    let completion_marked_buffer = indoc! {"
13631        1. ooanb
13632        2. zooanb
13633        3. ooanbz
13634        4. zooanbz
13635
13636        <oo|anb>
13637    "};
13638    let expected = indoc! {"
13639        1. foo_and_barˇ
13640        2. zfoo_and_barˇ
13641        3. foo_and_barˇz
13642        4. zfoo_and_barˇz
13643
13644        foo_and_barˇ
13645    "};
13646    cx.set_state(initial_state);
13647    cx.update_editor(|editor, window, cx| {
13648        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13649    });
13650    handle_completion_request_with_insert_and_replace(
13651        &mut cx,
13652        completion_marked_buffer,
13653        vec![(completion_text, completion_text)],
13654        Arc::new(AtomicUsize::new(0)),
13655    )
13656    .await;
13657    cx.condition(|editor, _| editor.context_menu_visible())
13658        .await;
13659    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13660        editor
13661            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13662            .unwrap()
13663    });
13664    cx.assert_editor_state(expected);
13665    handle_resolve_completion_request(&mut cx, None).await;
13666    apply_additional_edits.await.unwrap();
13667}
13668
13669// This used to crash
13670#[gpui::test]
13671async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
13672    init_test(cx, |_| {});
13673
13674    let buffer_text = indoc! {"
13675        fn main() {
13676            10.satu;
13677
13678            //
13679            // separate cursors so they open in different excerpts (manually reproducible)
13680            //
13681
13682            10.satu20;
13683        }
13684    "};
13685    let multibuffer_text_with_selections = indoc! {"
13686        fn main() {
13687            10.satuˇ;
13688
13689            //
13690
13691            //
13692
13693            10.satuˇ20;
13694        }
13695    "};
13696    let expected_multibuffer = indoc! {"
13697        fn main() {
13698            10.saturating_sub()ˇ;
13699
13700            //
13701
13702            //
13703
13704            10.saturating_sub()ˇ;
13705        }
13706    "};
13707
13708    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
13709    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
13710
13711    let fs = FakeFs::new(cx.executor());
13712    fs.insert_tree(
13713        path!("/a"),
13714        json!({
13715            "main.rs": buffer_text,
13716        }),
13717    )
13718    .await;
13719
13720    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13721    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13722    language_registry.add(rust_lang());
13723    let mut fake_servers = language_registry.register_fake_lsp(
13724        "Rust",
13725        FakeLspAdapter {
13726            capabilities: lsp::ServerCapabilities {
13727                completion_provider: Some(lsp::CompletionOptions {
13728                    resolve_provider: None,
13729                    ..lsp::CompletionOptions::default()
13730                }),
13731                ..lsp::ServerCapabilities::default()
13732            },
13733            ..FakeLspAdapter::default()
13734        },
13735    );
13736    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13737    let cx = &mut VisualTestContext::from_window(*workspace, cx);
13738    let buffer = project
13739        .update(cx, |project, cx| {
13740            project.open_local_buffer(path!("/a/main.rs"), cx)
13741        })
13742        .await
13743        .unwrap();
13744
13745    let multi_buffer = cx.new(|cx| {
13746        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
13747        multi_buffer.push_excerpts(
13748            buffer.clone(),
13749            [ExcerptRange::new(0..first_excerpt_end)],
13750            cx,
13751        );
13752        multi_buffer.push_excerpts(
13753            buffer.clone(),
13754            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
13755            cx,
13756        );
13757        multi_buffer
13758    });
13759
13760    let editor = workspace
13761        .update(cx, |_, window, cx| {
13762            cx.new(|cx| {
13763                Editor::new(
13764                    EditorMode::Full {
13765                        scale_ui_elements_with_buffer_font_size: false,
13766                        show_active_line_background: false,
13767                        sized_by_content: false,
13768                    },
13769                    multi_buffer.clone(),
13770                    Some(project.clone()),
13771                    window,
13772                    cx,
13773                )
13774            })
13775        })
13776        .unwrap();
13777
13778    let pane = workspace
13779        .update(cx, |workspace, _, _| workspace.active_pane().clone())
13780        .unwrap();
13781    pane.update_in(cx, |pane, window, cx| {
13782        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
13783    });
13784
13785    let fake_server = fake_servers.next().await.unwrap();
13786
13787    editor.update_in(cx, |editor, window, cx| {
13788        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13789            s.select_ranges([
13790                Point::new(1, 11)..Point::new(1, 11),
13791                Point::new(7, 11)..Point::new(7, 11),
13792            ])
13793        });
13794
13795        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
13796    });
13797
13798    editor.update_in(cx, |editor, window, cx| {
13799        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13800    });
13801
13802    fake_server
13803        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13804            let completion_item = lsp::CompletionItem {
13805                label: "saturating_sub()".into(),
13806                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
13807                    lsp::InsertReplaceEdit {
13808                        new_text: "saturating_sub()".to_owned(),
13809                        insert: lsp::Range::new(
13810                            lsp::Position::new(7, 7),
13811                            lsp::Position::new(7, 11),
13812                        ),
13813                        replace: lsp::Range::new(
13814                            lsp::Position::new(7, 7),
13815                            lsp::Position::new(7, 13),
13816                        ),
13817                    },
13818                )),
13819                ..lsp::CompletionItem::default()
13820            };
13821
13822            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
13823        })
13824        .next()
13825        .await
13826        .unwrap();
13827
13828    cx.condition(&editor, |editor, _| editor.context_menu_visible())
13829        .await;
13830
13831    editor
13832        .update_in(cx, |editor, window, cx| {
13833            editor
13834                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13835                .unwrap()
13836        })
13837        .await
13838        .unwrap();
13839
13840    editor.update(cx, |editor, cx| {
13841        assert_text_with_selections(editor, expected_multibuffer, cx);
13842    })
13843}
13844
13845#[gpui::test]
13846async fn test_completion(cx: &mut TestAppContext) {
13847    init_test(cx, |_| {});
13848
13849    let mut cx = EditorLspTestContext::new_rust(
13850        lsp::ServerCapabilities {
13851            completion_provider: Some(lsp::CompletionOptions {
13852                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
13853                resolve_provider: Some(true),
13854                ..Default::default()
13855            }),
13856            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13857            ..Default::default()
13858        },
13859        cx,
13860    )
13861    .await;
13862    let counter = Arc::new(AtomicUsize::new(0));
13863
13864    cx.set_state(indoc! {"
13865        oneˇ
13866        two
13867        three
13868    "});
13869    cx.simulate_keystroke(".");
13870    handle_completion_request(
13871        indoc! {"
13872            one.|<>
13873            two
13874            three
13875        "},
13876        vec!["first_completion", "second_completion"],
13877        true,
13878        counter.clone(),
13879        &mut cx,
13880    )
13881    .await;
13882    cx.condition(|editor, _| editor.context_menu_visible())
13883        .await;
13884    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13885
13886    let _handler = handle_signature_help_request(
13887        &mut cx,
13888        lsp::SignatureHelp {
13889            signatures: vec![lsp::SignatureInformation {
13890                label: "test signature".to_string(),
13891                documentation: None,
13892                parameters: Some(vec![lsp::ParameterInformation {
13893                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
13894                    documentation: None,
13895                }]),
13896                active_parameter: None,
13897            }],
13898            active_signature: None,
13899            active_parameter: None,
13900        },
13901    );
13902    cx.update_editor(|editor, window, cx| {
13903        assert!(
13904            !editor.signature_help_state.is_shown(),
13905            "No signature help was called for"
13906        );
13907        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13908    });
13909    cx.run_until_parked();
13910    cx.update_editor(|editor, _, _| {
13911        assert!(
13912            !editor.signature_help_state.is_shown(),
13913            "No signature help should be shown when completions menu is open"
13914        );
13915    });
13916
13917    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13918        editor.context_menu_next(&Default::default(), window, cx);
13919        editor
13920            .confirm_completion(&ConfirmCompletion::default(), window, cx)
13921            .unwrap()
13922    });
13923    cx.assert_editor_state(indoc! {"
13924        one.second_completionˇ
13925        two
13926        three
13927    "});
13928
13929    handle_resolve_completion_request(
13930        &mut cx,
13931        Some(vec![
13932            (
13933                //This overlaps with the primary completion edit which is
13934                //misbehavior from the LSP spec, test that we filter it out
13935                indoc! {"
13936                    one.second_ˇcompletion
13937                    two
13938                    threeˇ
13939                "},
13940                "overlapping additional edit",
13941            ),
13942            (
13943                indoc! {"
13944                    one.second_completion
13945                    two
13946                    threeˇ
13947                "},
13948                "\nadditional edit",
13949            ),
13950        ]),
13951    )
13952    .await;
13953    apply_additional_edits.await.unwrap();
13954    cx.assert_editor_state(indoc! {"
13955        one.second_completionˇ
13956        two
13957        three
13958        additional edit
13959    "});
13960
13961    cx.set_state(indoc! {"
13962        one.second_completion
13963        twoˇ
13964        threeˇ
13965        additional edit
13966    "});
13967    cx.simulate_keystroke(" ");
13968    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
13969    cx.simulate_keystroke("s");
13970    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
13971
13972    cx.assert_editor_state(indoc! {"
13973        one.second_completion
13974        two sˇ
13975        three sˇ
13976        additional edit
13977    "});
13978    handle_completion_request(
13979        indoc! {"
13980            one.second_completion
13981            two s
13982            three <s|>
13983            additional edit
13984        "},
13985        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
13986        true,
13987        counter.clone(),
13988        &mut cx,
13989    )
13990    .await;
13991    cx.condition(|editor, _| editor.context_menu_visible())
13992        .await;
13993    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
13994
13995    cx.simulate_keystroke("i");
13996
13997    handle_completion_request(
13998        indoc! {"
13999            one.second_completion
14000            two si
14001            three <si|>
14002            additional edit
14003        "},
14004        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14005        true,
14006        counter.clone(),
14007        &mut cx,
14008    )
14009    .await;
14010    cx.condition(|editor, _| editor.context_menu_visible())
14011        .await;
14012    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14013
14014    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14015        editor
14016            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14017            .unwrap()
14018    });
14019    cx.assert_editor_state(indoc! {"
14020        one.second_completion
14021        two sixth_completionˇ
14022        three sixth_completionˇ
14023        additional edit
14024    "});
14025
14026    apply_additional_edits.await.unwrap();
14027
14028    update_test_language_settings(&mut cx, |settings| {
14029        settings.defaults.show_completions_on_input = Some(false);
14030    });
14031    cx.set_state("editorˇ");
14032    cx.simulate_keystroke(".");
14033    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14034    cx.simulate_keystrokes("c l o");
14035    cx.assert_editor_state("editor.cloˇ");
14036    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14037    cx.update_editor(|editor, window, cx| {
14038        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14039    });
14040    handle_completion_request(
14041        "editor.<clo|>",
14042        vec!["close", "clobber"],
14043        true,
14044        counter.clone(),
14045        &mut cx,
14046    )
14047    .await;
14048    cx.condition(|editor, _| editor.context_menu_visible())
14049        .await;
14050    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
14051
14052    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14053        editor
14054            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14055            .unwrap()
14056    });
14057    cx.assert_editor_state("editor.clobberˇ");
14058    handle_resolve_completion_request(&mut cx, None).await;
14059    apply_additional_edits.await.unwrap();
14060}
14061
14062#[gpui::test]
14063async fn test_completion_reuse(cx: &mut TestAppContext) {
14064    init_test(cx, |_| {});
14065
14066    let mut cx = EditorLspTestContext::new_rust(
14067        lsp::ServerCapabilities {
14068            completion_provider: Some(lsp::CompletionOptions {
14069                trigger_characters: Some(vec![".".to_string()]),
14070                ..Default::default()
14071            }),
14072            ..Default::default()
14073        },
14074        cx,
14075    )
14076    .await;
14077
14078    let counter = Arc::new(AtomicUsize::new(0));
14079    cx.set_state("objˇ");
14080    cx.simulate_keystroke(".");
14081
14082    // Initial completion request returns complete results
14083    let is_incomplete = false;
14084    handle_completion_request(
14085        "obj.|<>",
14086        vec!["a", "ab", "abc"],
14087        is_incomplete,
14088        counter.clone(),
14089        &mut cx,
14090    )
14091    .await;
14092    cx.run_until_parked();
14093    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14094    cx.assert_editor_state("obj.ˇ");
14095    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14096
14097    // Type "a" - filters existing completions
14098    cx.simulate_keystroke("a");
14099    cx.run_until_parked();
14100    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14101    cx.assert_editor_state("obj.aˇ");
14102    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14103
14104    // Type "b" - filters existing completions
14105    cx.simulate_keystroke("b");
14106    cx.run_until_parked();
14107    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14108    cx.assert_editor_state("obj.abˇ");
14109    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14110
14111    // Type "c" - filters existing completions
14112    cx.simulate_keystroke("c");
14113    cx.run_until_parked();
14114    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14115    cx.assert_editor_state("obj.abcˇ");
14116    check_displayed_completions(vec!["abc"], &mut cx);
14117
14118    // Backspace to delete "c" - filters existing completions
14119    cx.update_editor(|editor, window, cx| {
14120        editor.backspace(&Backspace, window, cx);
14121    });
14122    cx.run_until_parked();
14123    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14124    cx.assert_editor_state("obj.abˇ");
14125    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14126
14127    // Moving cursor to the left dismisses menu.
14128    cx.update_editor(|editor, window, cx| {
14129        editor.move_left(&MoveLeft, window, cx);
14130    });
14131    cx.run_until_parked();
14132    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14133    cx.assert_editor_state("obj.aˇb");
14134    cx.update_editor(|editor, _, _| {
14135        assert_eq!(editor.context_menu_visible(), false);
14136    });
14137
14138    // Type "b" - new request
14139    cx.simulate_keystroke("b");
14140    let is_incomplete = false;
14141    handle_completion_request(
14142        "obj.<ab|>a",
14143        vec!["ab", "abc"],
14144        is_incomplete,
14145        counter.clone(),
14146        &mut cx,
14147    )
14148    .await;
14149    cx.run_until_parked();
14150    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14151    cx.assert_editor_state("obj.abˇb");
14152    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14153
14154    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
14155    cx.update_editor(|editor, window, cx| {
14156        editor.backspace(&Backspace, window, cx);
14157    });
14158    let is_incomplete = false;
14159    handle_completion_request(
14160        "obj.<a|>b",
14161        vec!["a", "ab", "abc"],
14162        is_incomplete,
14163        counter.clone(),
14164        &mut cx,
14165    )
14166    .await;
14167    cx.run_until_parked();
14168    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14169    cx.assert_editor_state("obj.aˇb");
14170    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14171
14172    // Backspace to delete "a" - dismisses menu.
14173    cx.update_editor(|editor, window, cx| {
14174        editor.backspace(&Backspace, window, cx);
14175    });
14176    cx.run_until_parked();
14177    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14178    cx.assert_editor_state("obj.ˇb");
14179    cx.update_editor(|editor, _, _| {
14180        assert_eq!(editor.context_menu_visible(), false);
14181    });
14182}
14183
14184#[gpui::test]
14185async fn test_word_completion(cx: &mut TestAppContext) {
14186    let lsp_fetch_timeout_ms = 10;
14187    init_test(cx, |language_settings| {
14188        language_settings.defaults.completions = Some(CompletionSettings {
14189            words: WordsCompletionMode::Fallback,
14190            words_min_length: 0,
14191            lsp: true,
14192            lsp_fetch_timeout_ms: 10,
14193            lsp_insert_mode: LspInsertMode::Insert,
14194        });
14195    });
14196
14197    let mut cx = EditorLspTestContext::new_rust(
14198        lsp::ServerCapabilities {
14199            completion_provider: Some(lsp::CompletionOptions {
14200                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14201                ..lsp::CompletionOptions::default()
14202            }),
14203            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14204            ..lsp::ServerCapabilities::default()
14205        },
14206        cx,
14207    )
14208    .await;
14209
14210    let throttle_completions = Arc::new(AtomicBool::new(false));
14211
14212    let lsp_throttle_completions = throttle_completions.clone();
14213    let _completion_requests_handler =
14214        cx.lsp
14215            .server
14216            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
14217                let lsp_throttle_completions = lsp_throttle_completions.clone();
14218                let cx = cx.clone();
14219                async move {
14220                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
14221                        cx.background_executor()
14222                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
14223                            .await;
14224                    }
14225                    Ok(Some(lsp::CompletionResponse::Array(vec![
14226                        lsp::CompletionItem {
14227                            label: "first".into(),
14228                            ..lsp::CompletionItem::default()
14229                        },
14230                        lsp::CompletionItem {
14231                            label: "last".into(),
14232                            ..lsp::CompletionItem::default()
14233                        },
14234                    ])))
14235                }
14236            });
14237
14238    cx.set_state(indoc! {"
14239        oneˇ
14240        two
14241        three
14242    "});
14243    cx.simulate_keystroke(".");
14244    cx.executor().run_until_parked();
14245    cx.condition(|editor, _| editor.context_menu_visible())
14246        .await;
14247    cx.update_editor(|editor, window, cx| {
14248        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14249        {
14250            assert_eq!(
14251                completion_menu_entries(menu),
14252                &["first", "last"],
14253                "When LSP server is fast to reply, no fallback word completions are used"
14254            );
14255        } else {
14256            panic!("expected completion menu to be open");
14257        }
14258        editor.cancel(&Cancel, window, cx);
14259    });
14260    cx.executor().run_until_parked();
14261    cx.condition(|editor, _| !editor.context_menu_visible())
14262        .await;
14263
14264    throttle_completions.store(true, atomic::Ordering::Release);
14265    cx.simulate_keystroke(".");
14266    cx.executor()
14267        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
14268    cx.executor().run_until_parked();
14269    cx.condition(|editor, _| editor.context_menu_visible())
14270        .await;
14271    cx.update_editor(|editor, _, _| {
14272        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14273        {
14274            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
14275                "When LSP server is slow, document words can be shown instead, if configured accordingly");
14276        } else {
14277            panic!("expected completion menu to be open");
14278        }
14279    });
14280}
14281
14282#[gpui::test]
14283async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
14284    init_test(cx, |language_settings| {
14285        language_settings.defaults.completions = Some(CompletionSettings {
14286            words: WordsCompletionMode::Enabled,
14287            words_min_length: 0,
14288            lsp: true,
14289            lsp_fetch_timeout_ms: 0,
14290            lsp_insert_mode: LspInsertMode::Insert,
14291        });
14292    });
14293
14294    let mut cx = EditorLspTestContext::new_rust(
14295        lsp::ServerCapabilities {
14296            completion_provider: Some(lsp::CompletionOptions {
14297                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14298                ..lsp::CompletionOptions::default()
14299            }),
14300            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14301            ..lsp::ServerCapabilities::default()
14302        },
14303        cx,
14304    )
14305    .await;
14306
14307    let _completion_requests_handler =
14308        cx.lsp
14309            .server
14310            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14311                Ok(Some(lsp::CompletionResponse::Array(vec![
14312                    lsp::CompletionItem {
14313                        label: "first".into(),
14314                        ..lsp::CompletionItem::default()
14315                    },
14316                    lsp::CompletionItem {
14317                        label: "last".into(),
14318                        ..lsp::CompletionItem::default()
14319                    },
14320                ])))
14321            });
14322
14323    cx.set_state(indoc! {"ˇ
14324        first
14325        last
14326        second
14327    "});
14328    cx.simulate_keystroke(".");
14329    cx.executor().run_until_parked();
14330    cx.condition(|editor, _| editor.context_menu_visible())
14331        .await;
14332    cx.update_editor(|editor, _, _| {
14333        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14334        {
14335            assert_eq!(
14336                completion_menu_entries(menu),
14337                &["first", "last", "second"],
14338                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
14339            );
14340        } else {
14341            panic!("expected completion menu to be open");
14342        }
14343    });
14344}
14345
14346#[gpui::test]
14347async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
14348    init_test(cx, |language_settings| {
14349        language_settings.defaults.completions = Some(CompletionSettings {
14350            words: WordsCompletionMode::Disabled,
14351            words_min_length: 0,
14352            lsp: true,
14353            lsp_fetch_timeout_ms: 0,
14354            lsp_insert_mode: LspInsertMode::Insert,
14355        });
14356    });
14357
14358    let mut cx = EditorLspTestContext::new_rust(
14359        lsp::ServerCapabilities {
14360            completion_provider: Some(lsp::CompletionOptions {
14361                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14362                ..lsp::CompletionOptions::default()
14363            }),
14364            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14365            ..lsp::ServerCapabilities::default()
14366        },
14367        cx,
14368    )
14369    .await;
14370
14371    let _completion_requests_handler =
14372        cx.lsp
14373            .server
14374            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14375                panic!("LSP completions should not be queried when dealing with word completions")
14376            });
14377
14378    cx.set_state(indoc! {"ˇ
14379        first
14380        last
14381        second
14382    "});
14383    cx.update_editor(|editor, window, cx| {
14384        editor.show_word_completions(&ShowWordCompletions, window, cx);
14385    });
14386    cx.executor().run_until_parked();
14387    cx.condition(|editor, _| editor.context_menu_visible())
14388        .await;
14389    cx.update_editor(|editor, _, _| {
14390        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14391        {
14392            assert_eq!(
14393                completion_menu_entries(menu),
14394                &["first", "last", "second"],
14395                "`ShowWordCompletions` action should show word completions"
14396            );
14397        } else {
14398            panic!("expected completion menu to be open");
14399        }
14400    });
14401
14402    cx.simulate_keystroke("l");
14403    cx.executor().run_until_parked();
14404    cx.condition(|editor, _| editor.context_menu_visible())
14405        .await;
14406    cx.update_editor(|editor, _, _| {
14407        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14408        {
14409            assert_eq!(
14410                completion_menu_entries(menu),
14411                &["last"],
14412                "After showing word completions, further editing should filter them and not query the LSP"
14413            );
14414        } else {
14415            panic!("expected completion menu to be open");
14416        }
14417    });
14418}
14419
14420#[gpui::test]
14421async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
14422    init_test(cx, |language_settings| {
14423        language_settings.defaults.completions = Some(CompletionSettings {
14424            words: WordsCompletionMode::Fallback,
14425            words_min_length: 0,
14426            lsp: false,
14427            lsp_fetch_timeout_ms: 0,
14428            lsp_insert_mode: LspInsertMode::Insert,
14429        });
14430    });
14431
14432    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14433
14434    cx.set_state(indoc! {"ˇ
14435        0_usize
14436        let
14437        33
14438        4.5f32
14439    "});
14440    cx.update_editor(|editor, window, cx| {
14441        editor.show_completions(&ShowCompletions::default(), window, cx);
14442    });
14443    cx.executor().run_until_parked();
14444    cx.condition(|editor, _| editor.context_menu_visible())
14445        .await;
14446    cx.update_editor(|editor, window, cx| {
14447        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14448        {
14449            assert_eq!(
14450                completion_menu_entries(menu),
14451                &["let"],
14452                "With no digits in the completion query, no digits should be in the word completions"
14453            );
14454        } else {
14455            panic!("expected completion menu to be open");
14456        }
14457        editor.cancel(&Cancel, window, cx);
14458    });
14459
14460    cx.set_state(indoc! {"14461        0_usize
14462        let
14463        3
14464        33.35f32
14465    "});
14466    cx.update_editor(|editor, window, cx| {
14467        editor.show_completions(&ShowCompletions::default(), window, cx);
14468    });
14469    cx.executor().run_until_parked();
14470    cx.condition(|editor, _| editor.context_menu_visible())
14471        .await;
14472    cx.update_editor(|editor, _, _| {
14473        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14474        {
14475            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
14476                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
14477        } else {
14478            panic!("expected completion menu to be open");
14479        }
14480    });
14481}
14482
14483#[gpui::test]
14484async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
14485    init_test(cx, |language_settings| {
14486        language_settings.defaults.completions = Some(CompletionSettings {
14487            words: WordsCompletionMode::Enabled,
14488            words_min_length: 3,
14489            lsp: true,
14490            lsp_fetch_timeout_ms: 0,
14491            lsp_insert_mode: LspInsertMode::Insert,
14492        });
14493    });
14494
14495    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14496    cx.set_state(indoc! {"ˇ
14497        wow
14498        wowen
14499        wowser
14500    "});
14501    cx.simulate_keystroke("w");
14502    cx.executor().run_until_parked();
14503    cx.update_editor(|editor, _, _| {
14504        if editor.context_menu.borrow_mut().is_some() {
14505            panic!(
14506                "expected completion menu to be hidden, as words completion threshold is not met"
14507            );
14508        }
14509    });
14510
14511    cx.update_editor(|editor, window, cx| {
14512        editor.show_word_completions(&ShowWordCompletions, window, cx);
14513    });
14514    cx.executor().run_until_parked();
14515    cx.update_editor(|editor, window, cx| {
14516        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14517        {
14518            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");
14519        } else {
14520            panic!("expected completion menu to be open after the word completions are called with an action");
14521        }
14522
14523        editor.cancel(&Cancel, window, cx);
14524    });
14525    cx.update_editor(|editor, _, _| {
14526        if editor.context_menu.borrow_mut().is_some() {
14527            panic!("expected completion menu to be hidden after canceling");
14528        }
14529    });
14530
14531    cx.simulate_keystroke("o");
14532    cx.executor().run_until_parked();
14533    cx.update_editor(|editor, _, _| {
14534        if editor.context_menu.borrow_mut().is_some() {
14535            panic!(
14536                "expected completion menu to be hidden, as words completion threshold is not met still"
14537            );
14538        }
14539    });
14540
14541    cx.simulate_keystroke("w");
14542    cx.executor().run_until_parked();
14543    cx.update_editor(|editor, _, _| {
14544        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14545        {
14546            assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
14547        } else {
14548            panic!("expected completion menu to be open after the word completions threshold is met");
14549        }
14550    });
14551}
14552
14553#[gpui::test]
14554async fn test_word_completions_disabled(cx: &mut TestAppContext) {
14555    init_test(cx, |language_settings| {
14556        language_settings.defaults.completions = Some(CompletionSettings {
14557            words: WordsCompletionMode::Enabled,
14558            words_min_length: 0,
14559            lsp: true,
14560            lsp_fetch_timeout_ms: 0,
14561            lsp_insert_mode: LspInsertMode::Insert,
14562        });
14563    });
14564
14565    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14566    cx.update_editor(|editor, _, _| {
14567        editor.disable_word_completions();
14568    });
14569    cx.set_state(indoc! {"ˇ
14570        wow
14571        wowen
14572        wowser
14573    "});
14574    cx.simulate_keystroke("w");
14575    cx.executor().run_until_parked();
14576    cx.update_editor(|editor, _, _| {
14577        if editor.context_menu.borrow_mut().is_some() {
14578            panic!(
14579                "expected completion menu to be hidden, as words completion are disabled for this editor"
14580            );
14581        }
14582    });
14583
14584    cx.update_editor(|editor, window, cx| {
14585        editor.show_word_completions(&ShowWordCompletions, window, cx);
14586    });
14587    cx.executor().run_until_parked();
14588    cx.update_editor(|editor, _, _| {
14589        if editor.context_menu.borrow_mut().is_some() {
14590            panic!(
14591                "expected completion menu to be hidden even if called for explicitly, as words completion are disabled for this editor"
14592            );
14593        }
14594    });
14595}
14596
14597fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
14598    let position = || lsp::Position {
14599        line: params.text_document_position.position.line,
14600        character: params.text_document_position.position.character,
14601    };
14602    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14603        range: lsp::Range {
14604            start: position(),
14605            end: position(),
14606        },
14607        new_text: text.to_string(),
14608    }))
14609}
14610
14611#[gpui::test]
14612async fn test_multiline_completion(cx: &mut TestAppContext) {
14613    init_test(cx, |_| {});
14614
14615    let fs = FakeFs::new(cx.executor());
14616    fs.insert_tree(
14617        path!("/a"),
14618        json!({
14619            "main.ts": "a",
14620        }),
14621    )
14622    .await;
14623
14624    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14625    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14626    let typescript_language = Arc::new(Language::new(
14627        LanguageConfig {
14628            name: "TypeScript".into(),
14629            matcher: LanguageMatcher {
14630                path_suffixes: vec!["ts".to_string()],
14631                ..LanguageMatcher::default()
14632            },
14633            line_comments: vec!["// ".into()],
14634            ..LanguageConfig::default()
14635        },
14636        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
14637    ));
14638    language_registry.add(typescript_language.clone());
14639    let mut fake_servers = language_registry.register_fake_lsp(
14640        "TypeScript",
14641        FakeLspAdapter {
14642            capabilities: lsp::ServerCapabilities {
14643                completion_provider: Some(lsp::CompletionOptions {
14644                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14645                    ..lsp::CompletionOptions::default()
14646                }),
14647                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14648                ..lsp::ServerCapabilities::default()
14649            },
14650            // Emulate vtsls label generation
14651            label_for_completion: Some(Box::new(|item, _| {
14652                let text = if let Some(description) = item
14653                    .label_details
14654                    .as_ref()
14655                    .and_then(|label_details| label_details.description.as_ref())
14656                {
14657                    format!("{} {}", item.label, description)
14658                } else if let Some(detail) = &item.detail {
14659                    format!("{} {}", item.label, detail)
14660                } else {
14661                    item.label.clone()
14662                };
14663                let len = text.len();
14664                Some(language::CodeLabel {
14665                    text,
14666                    runs: Vec::new(),
14667                    filter_range: 0..len,
14668                })
14669            })),
14670            ..FakeLspAdapter::default()
14671        },
14672    );
14673    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14674    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14675    let worktree_id = workspace
14676        .update(cx, |workspace, _window, cx| {
14677            workspace.project().update(cx, |project, cx| {
14678                project.worktrees(cx).next().unwrap().read(cx).id()
14679            })
14680        })
14681        .unwrap();
14682    let _buffer = project
14683        .update(cx, |project, cx| {
14684            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
14685        })
14686        .await
14687        .unwrap();
14688    let editor = workspace
14689        .update(cx, |workspace, window, cx| {
14690            workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
14691        })
14692        .unwrap()
14693        .await
14694        .unwrap()
14695        .downcast::<Editor>()
14696        .unwrap();
14697    let fake_server = fake_servers.next().await.unwrap();
14698
14699    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
14700    let multiline_label_2 = "a\nb\nc\n";
14701    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
14702    let multiline_description = "d\ne\nf\n";
14703    let multiline_detail_2 = "g\nh\ni\n";
14704
14705    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
14706        move |params, _| async move {
14707            Ok(Some(lsp::CompletionResponse::Array(vec![
14708                lsp::CompletionItem {
14709                    label: multiline_label.to_string(),
14710                    text_edit: gen_text_edit(&params, "new_text_1"),
14711                    ..lsp::CompletionItem::default()
14712                },
14713                lsp::CompletionItem {
14714                    label: "single line label 1".to_string(),
14715                    detail: Some(multiline_detail.to_string()),
14716                    text_edit: gen_text_edit(&params, "new_text_2"),
14717                    ..lsp::CompletionItem::default()
14718                },
14719                lsp::CompletionItem {
14720                    label: "single line label 2".to_string(),
14721                    label_details: Some(lsp::CompletionItemLabelDetails {
14722                        description: Some(multiline_description.to_string()),
14723                        detail: None,
14724                    }),
14725                    text_edit: gen_text_edit(&params, "new_text_2"),
14726                    ..lsp::CompletionItem::default()
14727                },
14728                lsp::CompletionItem {
14729                    label: multiline_label_2.to_string(),
14730                    detail: Some(multiline_detail_2.to_string()),
14731                    text_edit: gen_text_edit(&params, "new_text_3"),
14732                    ..lsp::CompletionItem::default()
14733                },
14734                lsp::CompletionItem {
14735                    label: "Label with many     spaces and \t but without newlines".to_string(),
14736                    detail: Some(
14737                        "Details with many     spaces and \t but without newlines".to_string(),
14738                    ),
14739                    text_edit: gen_text_edit(&params, "new_text_4"),
14740                    ..lsp::CompletionItem::default()
14741                },
14742            ])))
14743        },
14744    );
14745
14746    editor.update_in(cx, |editor, window, cx| {
14747        cx.focus_self(window);
14748        editor.move_to_end(&MoveToEnd, window, cx);
14749        editor.handle_input(".", window, cx);
14750    });
14751    cx.run_until_parked();
14752    completion_handle.next().await.unwrap();
14753
14754    editor.update(cx, |editor, _| {
14755        assert!(editor.context_menu_visible());
14756        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14757        {
14758            let completion_labels = menu
14759                .completions
14760                .borrow()
14761                .iter()
14762                .map(|c| c.label.text.clone())
14763                .collect::<Vec<_>>();
14764            assert_eq!(
14765                completion_labels,
14766                &[
14767                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
14768                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
14769                    "single line label 2 d e f ",
14770                    "a b c g h i ",
14771                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
14772                ],
14773                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
14774            );
14775
14776            for completion in menu
14777                .completions
14778                .borrow()
14779                .iter() {
14780                    assert_eq!(
14781                        completion.label.filter_range,
14782                        0..completion.label.text.len(),
14783                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
14784                    );
14785                }
14786        } else {
14787            panic!("expected completion menu to be open");
14788        }
14789    });
14790}
14791
14792#[gpui::test]
14793async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
14794    init_test(cx, |_| {});
14795    let mut cx = EditorLspTestContext::new_rust(
14796        lsp::ServerCapabilities {
14797            completion_provider: Some(lsp::CompletionOptions {
14798                trigger_characters: Some(vec![".".to_string()]),
14799                ..Default::default()
14800            }),
14801            ..Default::default()
14802        },
14803        cx,
14804    )
14805    .await;
14806    cx.lsp
14807        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14808            Ok(Some(lsp::CompletionResponse::Array(vec![
14809                lsp::CompletionItem {
14810                    label: "first".into(),
14811                    ..Default::default()
14812                },
14813                lsp::CompletionItem {
14814                    label: "last".into(),
14815                    ..Default::default()
14816                },
14817            ])))
14818        });
14819    cx.set_state("variableˇ");
14820    cx.simulate_keystroke(".");
14821    cx.executor().run_until_parked();
14822
14823    cx.update_editor(|editor, _, _| {
14824        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14825        {
14826            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
14827        } else {
14828            panic!("expected completion menu to be open");
14829        }
14830    });
14831
14832    cx.update_editor(|editor, window, cx| {
14833        editor.move_page_down(&MovePageDown::default(), window, cx);
14834        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14835        {
14836            assert!(
14837                menu.selected_item == 1,
14838                "expected PageDown to select the last item from the context menu"
14839            );
14840        } else {
14841            panic!("expected completion menu to stay open after PageDown");
14842        }
14843    });
14844
14845    cx.update_editor(|editor, window, cx| {
14846        editor.move_page_up(&MovePageUp::default(), window, cx);
14847        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14848        {
14849            assert!(
14850                menu.selected_item == 0,
14851                "expected PageUp to select the first item from the context menu"
14852            );
14853        } else {
14854            panic!("expected completion menu to stay open after PageUp");
14855        }
14856    });
14857}
14858
14859#[gpui::test]
14860async fn test_as_is_completions(cx: &mut TestAppContext) {
14861    init_test(cx, |_| {});
14862    let mut cx = EditorLspTestContext::new_rust(
14863        lsp::ServerCapabilities {
14864            completion_provider: Some(lsp::CompletionOptions {
14865                ..Default::default()
14866            }),
14867            ..Default::default()
14868        },
14869        cx,
14870    )
14871    .await;
14872    cx.lsp
14873        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14874            Ok(Some(lsp::CompletionResponse::Array(vec![
14875                lsp::CompletionItem {
14876                    label: "unsafe".into(),
14877                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14878                        range: lsp::Range {
14879                            start: lsp::Position {
14880                                line: 1,
14881                                character: 2,
14882                            },
14883                            end: lsp::Position {
14884                                line: 1,
14885                                character: 3,
14886                            },
14887                        },
14888                        new_text: "unsafe".to_string(),
14889                    })),
14890                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
14891                    ..Default::default()
14892                },
14893            ])))
14894        });
14895    cx.set_state("fn a() {}\n");
14896    cx.executor().run_until_parked();
14897    cx.update_editor(|editor, window, cx| {
14898        editor.show_completions(
14899            &ShowCompletions {
14900                trigger: Some("\n".into()),
14901            },
14902            window,
14903            cx,
14904        );
14905    });
14906    cx.executor().run_until_parked();
14907
14908    cx.update_editor(|editor, window, cx| {
14909        editor.confirm_completion(&Default::default(), window, cx)
14910    });
14911    cx.executor().run_until_parked();
14912    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
14913}
14914
14915#[gpui::test]
14916async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
14917    init_test(cx, |_| {});
14918    let language =
14919        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
14920    let mut cx = EditorLspTestContext::new(
14921        language,
14922        lsp::ServerCapabilities {
14923            completion_provider: Some(lsp::CompletionOptions {
14924                ..lsp::CompletionOptions::default()
14925            }),
14926            ..lsp::ServerCapabilities::default()
14927        },
14928        cx,
14929    )
14930    .await;
14931
14932    cx.set_state(
14933        "#ifndef BAR_H
14934#define BAR_H
14935
14936#include <stdbool.h>
14937
14938int fn_branch(bool do_branch1, bool do_branch2);
14939
14940#endif // BAR_H
14941ˇ",
14942    );
14943    cx.executor().run_until_parked();
14944    cx.update_editor(|editor, window, cx| {
14945        editor.handle_input("#", window, cx);
14946    });
14947    cx.executor().run_until_parked();
14948    cx.update_editor(|editor, window, cx| {
14949        editor.handle_input("i", window, cx);
14950    });
14951    cx.executor().run_until_parked();
14952    cx.update_editor(|editor, window, cx| {
14953        editor.handle_input("n", window, cx);
14954    });
14955    cx.executor().run_until_parked();
14956    cx.assert_editor_state(
14957        "#ifndef BAR_H
14958#define BAR_H
14959
14960#include <stdbool.h>
14961
14962int fn_branch(bool do_branch1, bool do_branch2);
14963
14964#endif // BAR_H
14965#inˇ",
14966    );
14967
14968    cx.lsp
14969        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14970            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
14971                is_incomplete: false,
14972                item_defaults: None,
14973                items: vec![lsp::CompletionItem {
14974                    kind: Some(lsp::CompletionItemKind::SNIPPET),
14975                    label_details: Some(lsp::CompletionItemLabelDetails {
14976                        detail: Some("header".to_string()),
14977                        description: None,
14978                    }),
14979                    label: " include".to_string(),
14980                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14981                        range: lsp::Range {
14982                            start: lsp::Position {
14983                                line: 8,
14984                                character: 1,
14985                            },
14986                            end: lsp::Position {
14987                                line: 8,
14988                                character: 1,
14989                            },
14990                        },
14991                        new_text: "include \"$0\"".to_string(),
14992                    })),
14993                    sort_text: Some("40b67681include".to_string()),
14994                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
14995                    filter_text: Some("include".to_string()),
14996                    insert_text: Some("include \"$0\"".to_string()),
14997                    ..lsp::CompletionItem::default()
14998                }],
14999            })))
15000        });
15001    cx.update_editor(|editor, window, cx| {
15002        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15003    });
15004    cx.executor().run_until_parked();
15005    cx.update_editor(|editor, window, cx| {
15006        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15007    });
15008    cx.executor().run_until_parked();
15009    cx.assert_editor_state(
15010        "#ifndef BAR_H
15011#define BAR_H
15012
15013#include <stdbool.h>
15014
15015int fn_branch(bool do_branch1, bool do_branch2);
15016
15017#endif // BAR_H
15018#include \"ˇ\"",
15019    );
15020
15021    cx.lsp
15022        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15023            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15024                is_incomplete: true,
15025                item_defaults: None,
15026                items: vec![lsp::CompletionItem {
15027                    kind: Some(lsp::CompletionItemKind::FILE),
15028                    label: "AGL/".to_string(),
15029                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15030                        range: lsp::Range {
15031                            start: lsp::Position {
15032                                line: 8,
15033                                character: 10,
15034                            },
15035                            end: lsp::Position {
15036                                line: 8,
15037                                character: 11,
15038                            },
15039                        },
15040                        new_text: "AGL/".to_string(),
15041                    })),
15042                    sort_text: Some("40b67681AGL/".to_string()),
15043                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
15044                    filter_text: Some("AGL/".to_string()),
15045                    insert_text: Some("AGL/".to_string()),
15046                    ..lsp::CompletionItem::default()
15047                }],
15048            })))
15049        });
15050    cx.update_editor(|editor, window, cx| {
15051        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15052    });
15053    cx.executor().run_until_parked();
15054    cx.update_editor(|editor, window, cx| {
15055        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15056    });
15057    cx.executor().run_until_parked();
15058    cx.assert_editor_state(
15059        r##"#ifndef BAR_H
15060#define BAR_H
15061
15062#include <stdbool.h>
15063
15064int fn_branch(bool do_branch1, bool do_branch2);
15065
15066#endif // BAR_H
15067#include "AGL/ˇ"##,
15068    );
15069
15070    cx.update_editor(|editor, window, cx| {
15071        editor.handle_input("\"", window, cx);
15072    });
15073    cx.executor().run_until_parked();
15074    cx.assert_editor_state(
15075        r##"#ifndef BAR_H
15076#define BAR_H
15077
15078#include <stdbool.h>
15079
15080int fn_branch(bool do_branch1, bool do_branch2);
15081
15082#endif // BAR_H
15083#include "AGL/"ˇ"##,
15084    );
15085}
15086
15087#[gpui::test]
15088async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
15089    init_test(cx, |_| {});
15090
15091    let mut cx = EditorLspTestContext::new_rust(
15092        lsp::ServerCapabilities {
15093            completion_provider: Some(lsp::CompletionOptions {
15094                trigger_characters: Some(vec![".".to_string()]),
15095                resolve_provider: Some(true),
15096                ..Default::default()
15097            }),
15098            ..Default::default()
15099        },
15100        cx,
15101    )
15102    .await;
15103
15104    cx.set_state("fn main() { let a = 2ˇ; }");
15105    cx.simulate_keystroke(".");
15106    let completion_item = lsp::CompletionItem {
15107        label: "Some".into(),
15108        kind: Some(lsp::CompletionItemKind::SNIPPET),
15109        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15110        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15111            kind: lsp::MarkupKind::Markdown,
15112            value: "```rust\nSome(2)\n```".to_string(),
15113        })),
15114        deprecated: Some(false),
15115        sort_text: Some("Some".to_string()),
15116        filter_text: Some("Some".to_string()),
15117        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15118        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15119            range: lsp::Range {
15120                start: lsp::Position {
15121                    line: 0,
15122                    character: 22,
15123                },
15124                end: lsp::Position {
15125                    line: 0,
15126                    character: 22,
15127                },
15128            },
15129            new_text: "Some(2)".to_string(),
15130        })),
15131        additional_text_edits: Some(vec![lsp::TextEdit {
15132            range: lsp::Range {
15133                start: lsp::Position {
15134                    line: 0,
15135                    character: 20,
15136                },
15137                end: lsp::Position {
15138                    line: 0,
15139                    character: 22,
15140                },
15141            },
15142            new_text: "".to_string(),
15143        }]),
15144        ..Default::default()
15145    };
15146
15147    let closure_completion_item = completion_item.clone();
15148    let counter = Arc::new(AtomicUsize::new(0));
15149    let counter_clone = counter.clone();
15150    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15151        let task_completion_item = closure_completion_item.clone();
15152        counter_clone.fetch_add(1, atomic::Ordering::Release);
15153        async move {
15154            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15155                is_incomplete: true,
15156                item_defaults: None,
15157                items: vec![task_completion_item],
15158            })))
15159        }
15160    });
15161
15162    cx.condition(|editor, _| editor.context_menu_visible())
15163        .await;
15164    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
15165    assert!(request.next().await.is_some());
15166    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15167
15168    cx.simulate_keystrokes("S o m");
15169    cx.condition(|editor, _| editor.context_menu_visible())
15170        .await;
15171    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
15172    assert!(request.next().await.is_some());
15173    assert!(request.next().await.is_some());
15174    assert!(request.next().await.is_some());
15175    request.close();
15176    assert!(request.next().await.is_none());
15177    assert_eq!(
15178        counter.load(atomic::Ordering::Acquire),
15179        4,
15180        "With the completions menu open, only one LSP request should happen per input"
15181    );
15182}
15183
15184#[gpui::test]
15185async fn test_toggle_comment(cx: &mut TestAppContext) {
15186    init_test(cx, |_| {});
15187    let mut cx = EditorTestContext::new(cx).await;
15188    let language = Arc::new(Language::new(
15189        LanguageConfig {
15190            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15191            ..Default::default()
15192        },
15193        Some(tree_sitter_rust::LANGUAGE.into()),
15194    ));
15195    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15196
15197    // If multiple selections intersect a line, the line is only toggled once.
15198    cx.set_state(indoc! {"
15199        fn a() {
15200            «//b();
15201            ˇ»// «c();
15202            //ˇ»  d();
15203        }
15204    "});
15205
15206    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15207
15208    cx.assert_editor_state(indoc! {"
15209        fn a() {
15210            «b();
15211            c();
15212            ˇ» d();
15213        }
15214    "});
15215
15216    // The comment prefix is inserted at the same column for every line in a
15217    // selection.
15218    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15219
15220    cx.assert_editor_state(indoc! {"
15221        fn a() {
15222            // «b();
15223            // c();
15224            ˇ»//  d();
15225        }
15226    "});
15227
15228    // If a selection ends at the beginning of a line, that line is not toggled.
15229    cx.set_selections_state(indoc! {"
15230        fn a() {
15231            // b();
15232            «// c();
15233        ˇ»    //  d();
15234        }
15235    "});
15236
15237    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15238
15239    cx.assert_editor_state(indoc! {"
15240        fn a() {
15241            // b();
15242            «c();
15243        ˇ»    //  d();
15244        }
15245    "});
15246
15247    // If a selection span a single line and is empty, the line is toggled.
15248    cx.set_state(indoc! {"
15249        fn a() {
15250            a();
15251            b();
15252        ˇ
15253        }
15254    "});
15255
15256    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15257
15258    cx.assert_editor_state(indoc! {"
15259        fn a() {
15260            a();
15261            b();
15262        //•ˇ
15263        }
15264    "});
15265
15266    // If a selection span multiple lines, empty lines are not toggled.
15267    cx.set_state(indoc! {"
15268        fn a() {
15269            «a();
15270
15271            c();ˇ»
15272        }
15273    "});
15274
15275    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15276
15277    cx.assert_editor_state(indoc! {"
15278        fn a() {
15279            // «a();
15280
15281            // c();ˇ»
15282        }
15283    "});
15284
15285    // If a selection includes multiple comment prefixes, all lines are uncommented.
15286    cx.set_state(indoc! {"
15287        fn a() {
15288            «// a();
15289            /// b();
15290            //! c();ˇ»
15291        }
15292    "});
15293
15294    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15295
15296    cx.assert_editor_state(indoc! {"
15297        fn a() {
15298            «a();
15299            b();
15300            c();ˇ»
15301        }
15302    "});
15303}
15304
15305#[gpui::test]
15306async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
15307    init_test(cx, |_| {});
15308    let mut cx = EditorTestContext::new(cx).await;
15309    let language = Arc::new(Language::new(
15310        LanguageConfig {
15311            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15312            ..Default::default()
15313        },
15314        Some(tree_sitter_rust::LANGUAGE.into()),
15315    ));
15316    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15317
15318    let toggle_comments = &ToggleComments {
15319        advance_downwards: false,
15320        ignore_indent: true,
15321    };
15322
15323    // If multiple selections intersect a line, the line is only toggled once.
15324    cx.set_state(indoc! {"
15325        fn a() {
15326        //    «b();
15327        //    c();
15328        //    ˇ» d();
15329        }
15330    "});
15331
15332    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15333
15334    cx.assert_editor_state(indoc! {"
15335        fn a() {
15336            «b();
15337            c();
15338            ˇ» d();
15339        }
15340    "});
15341
15342    // The comment prefix is inserted at the beginning of each line
15343    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15344
15345    cx.assert_editor_state(indoc! {"
15346        fn a() {
15347        //    «b();
15348        //    c();
15349        //    ˇ» d();
15350        }
15351    "});
15352
15353    // If a selection ends at the beginning of a line, that line is not toggled.
15354    cx.set_selections_state(indoc! {"
15355        fn a() {
15356        //    b();
15357        //    «c();
15358        ˇ»//     d();
15359        }
15360    "});
15361
15362    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15363
15364    cx.assert_editor_state(indoc! {"
15365        fn a() {
15366        //    b();
15367            «c();
15368        ˇ»//     d();
15369        }
15370    "});
15371
15372    // If a selection span a single line and is empty, the line is toggled.
15373    cx.set_state(indoc! {"
15374        fn a() {
15375            a();
15376            b();
15377        ˇ
15378        }
15379    "});
15380
15381    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15382
15383    cx.assert_editor_state(indoc! {"
15384        fn a() {
15385            a();
15386            b();
15387        //ˇ
15388        }
15389    "});
15390
15391    // If a selection span multiple lines, empty lines are not toggled.
15392    cx.set_state(indoc! {"
15393        fn a() {
15394            «a();
15395
15396            c();ˇ»
15397        }
15398    "});
15399
15400    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15401
15402    cx.assert_editor_state(indoc! {"
15403        fn a() {
15404        //    «a();
15405
15406        //    c();ˇ»
15407        }
15408    "});
15409
15410    // If a selection includes multiple comment prefixes, all lines are uncommented.
15411    cx.set_state(indoc! {"
15412        fn a() {
15413        //    «a();
15414        ///    b();
15415        //!    c();ˇ»
15416        }
15417    "});
15418
15419    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15420
15421    cx.assert_editor_state(indoc! {"
15422        fn a() {
15423            «a();
15424            b();
15425            c();ˇ»
15426        }
15427    "});
15428}
15429
15430#[gpui::test]
15431async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
15432    init_test(cx, |_| {});
15433
15434    let language = Arc::new(Language::new(
15435        LanguageConfig {
15436            line_comments: vec!["// ".into()],
15437            ..Default::default()
15438        },
15439        Some(tree_sitter_rust::LANGUAGE.into()),
15440    ));
15441
15442    let mut cx = EditorTestContext::new(cx).await;
15443
15444    cx.language_registry().add(language.clone());
15445    cx.update_buffer(|buffer, cx| {
15446        buffer.set_language(Some(language), cx);
15447    });
15448
15449    let toggle_comments = &ToggleComments {
15450        advance_downwards: true,
15451        ignore_indent: false,
15452    };
15453
15454    // Single cursor on one line -> advance
15455    // Cursor moves horizontally 3 characters as well on non-blank line
15456    cx.set_state(indoc!(
15457        "fn a() {
15458             ˇdog();
15459             cat();
15460        }"
15461    ));
15462    cx.update_editor(|editor, window, cx| {
15463        editor.toggle_comments(toggle_comments, window, cx);
15464    });
15465    cx.assert_editor_state(indoc!(
15466        "fn a() {
15467             // dog();
15468             catˇ();
15469        }"
15470    ));
15471
15472    // Single selection on one line -> don't advance
15473    cx.set_state(indoc!(
15474        "fn a() {
15475             «dog()ˇ»;
15476             cat();
15477        }"
15478    ));
15479    cx.update_editor(|editor, window, cx| {
15480        editor.toggle_comments(toggle_comments, window, cx);
15481    });
15482    cx.assert_editor_state(indoc!(
15483        "fn a() {
15484             // «dog()ˇ»;
15485             cat();
15486        }"
15487    ));
15488
15489    // Multiple cursors on one line -> advance
15490    cx.set_state(indoc!(
15491        "fn a() {
15492             ˇdˇog();
15493             cat();
15494        }"
15495    ));
15496    cx.update_editor(|editor, window, cx| {
15497        editor.toggle_comments(toggle_comments, window, cx);
15498    });
15499    cx.assert_editor_state(indoc!(
15500        "fn a() {
15501             // dog();
15502             catˇ(ˇ);
15503        }"
15504    ));
15505
15506    // Multiple cursors on one line, with selection -> don't advance
15507    cx.set_state(indoc!(
15508        "fn a() {
15509             ˇdˇog«()ˇ»;
15510             cat();
15511        }"
15512    ));
15513    cx.update_editor(|editor, window, cx| {
15514        editor.toggle_comments(toggle_comments, window, cx);
15515    });
15516    cx.assert_editor_state(indoc!(
15517        "fn a() {
15518             // ˇdˇog«()ˇ»;
15519             cat();
15520        }"
15521    ));
15522
15523    // Single cursor on one line -> advance
15524    // Cursor moves to column 0 on blank line
15525    cx.set_state(indoc!(
15526        "fn a() {
15527             ˇdog();
15528
15529             cat();
15530        }"
15531    ));
15532    cx.update_editor(|editor, window, cx| {
15533        editor.toggle_comments(toggle_comments, window, cx);
15534    });
15535    cx.assert_editor_state(indoc!(
15536        "fn a() {
15537             // dog();
15538        ˇ
15539             cat();
15540        }"
15541    ));
15542
15543    // Single cursor on one line -> advance
15544    // Cursor starts and ends at column 0
15545    cx.set_state(indoc!(
15546        "fn a() {
15547         ˇ    dog();
15548             cat();
15549        }"
15550    ));
15551    cx.update_editor(|editor, window, cx| {
15552        editor.toggle_comments(toggle_comments, window, cx);
15553    });
15554    cx.assert_editor_state(indoc!(
15555        "fn a() {
15556             // dog();
15557         ˇ    cat();
15558        }"
15559    ));
15560}
15561
15562#[gpui::test]
15563async fn test_toggle_block_comment(cx: &mut TestAppContext) {
15564    init_test(cx, |_| {});
15565
15566    let mut cx = EditorTestContext::new(cx).await;
15567
15568    let html_language = Arc::new(
15569        Language::new(
15570            LanguageConfig {
15571                name: "HTML".into(),
15572                block_comment: Some(BlockCommentConfig {
15573                    start: "<!-- ".into(),
15574                    prefix: "".into(),
15575                    end: " -->".into(),
15576                    tab_size: 0,
15577                }),
15578                ..Default::default()
15579            },
15580            Some(tree_sitter_html::LANGUAGE.into()),
15581        )
15582        .with_injection_query(
15583            r#"
15584            (script_element
15585                (raw_text) @injection.content
15586                (#set! injection.language "javascript"))
15587            "#,
15588        )
15589        .unwrap(),
15590    );
15591
15592    let javascript_language = Arc::new(Language::new(
15593        LanguageConfig {
15594            name: "JavaScript".into(),
15595            line_comments: vec!["// ".into()],
15596            ..Default::default()
15597        },
15598        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
15599    ));
15600
15601    cx.language_registry().add(html_language.clone());
15602    cx.language_registry().add(javascript_language);
15603    cx.update_buffer(|buffer, cx| {
15604        buffer.set_language(Some(html_language), cx);
15605    });
15606
15607    // Toggle comments for empty selections
15608    cx.set_state(
15609        &r#"
15610            <p>A</p>ˇ
15611            <p>B</p>ˇ
15612            <p>C</p>ˇ
15613        "#
15614        .unindent(),
15615    );
15616    cx.update_editor(|editor, window, cx| {
15617        editor.toggle_comments(&ToggleComments::default(), window, cx)
15618    });
15619    cx.assert_editor_state(
15620        &r#"
15621            <!-- <p>A</p>ˇ -->
15622            <!-- <p>B</p>ˇ -->
15623            <!-- <p>C</p>ˇ -->
15624        "#
15625        .unindent(),
15626    );
15627    cx.update_editor(|editor, window, cx| {
15628        editor.toggle_comments(&ToggleComments::default(), window, cx)
15629    });
15630    cx.assert_editor_state(
15631        &r#"
15632            <p>A</p>ˇ
15633            <p>B</p>ˇ
15634            <p>C</p>ˇ
15635        "#
15636        .unindent(),
15637    );
15638
15639    // Toggle comments for mixture of empty and non-empty selections, where
15640    // multiple selections occupy a given line.
15641    cx.set_state(
15642        &r#"
15643            <p>A«</p>
15644            <p>ˇ»B</p>ˇ
15645            <p>C«</p>
15646            <p>ˇ»D</p>ˇ
15647        "#
15648        .unindent(),
15649    );
15650
15651    cx.update_editor(|editor, window, cx| {
15652        editor.toggle_comments(&ToggleComments::default(), window, cx)
15653    });
15654    cx.assert_editor_state(
15655        &r#"
15656            <!-- <p>A«</p>
15657            <p>ˇ»B</p>ˇ -->
15658            <!-- <p>C«</p>
15659            <p>ˇ»D</p>ˇ -->
15660        "#
15661        .unindent(),
15662    );
15663    cx.update_editor(|editor, window, cx| {
15664        editor.toggle_comments(&ToggleComments::default(), window, cx)
15665    });
15666    cx.assert_editor_state(
15667        &r#"
15668            <p>A«</p>
15669            <p>ˇ»B</p>ˇ
15670            <p>C«</p>
15671            <p>ˇ»D</p>ˇ
15672        "#
15673        .unindent(),
15674    );
15675
15676    // Toggle comments when different languages are active for different
15677    // selections.
15678    cx.set_state(
15679        &r#"
15680            ˇ<script>
15681                ˇvar x = new Y();
15682            ˇ</script>
15683        "#
15684        .unindent(),
15685    );
15686    cx.executor().run_until_parked();
15687    cx.update_editor(|editor, window, cx| {
15688        editor.toggle_comments(&ToggleComments::default(), window, cx)
15689    });
15690    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
15691    // Uncommenting and commenting from this position brings in even more wrong artifacts.
15692    cx.assert_editor_state(
15693        &r#"
15694            <!-- ˇ<script> -->
15695                // ˇvar x = new Y();
15696            <!-- ˇ</script> -->
15697        "#
15698        .unindent(),
15699    );
15700}
15701
15702#[gpui::test]
15703fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
15704    init_test(cx, |_| {});
15705
15706    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15707    let multibuffer = cx.new(|cx| {
15708        let mut multibuffer = MultiBuffer::new(ReadWrite);
15709        multibuffer.push_excerpts(
15710            buffer.clone(),
15711            [
15712                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
15713                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
15714            ],
15715            cx,
15716        );
15717        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
15718        multibuffer
15719    });
15720
15721    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
15722    editor.update_in(cx, |editor, window, cx| {
15723        assert_eq!(editor.text(cx), "aaaa\nbbbb");
15724        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15725            s.select_ranges([
15726                Point::new(0, 0)..Point::new(0, 0),
15727                Point::new(1, 0)..Point::new(1, 0),
15728            ])
15729        });
15730
15731        editor.handle_input("X", window, cx);
15732        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
15733        assert_eq!(
15734            editor.selections.ranges(cx),
15735            [
15736                Point::new(0, 1)..Point::new(0, 1),
15737                Point::new(1, 1)..Point::new(1, 1),
15738            ]
15739        );
15740
15741        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
15742        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15743            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
15744        });
15745        editor.backspace(&Default::default(), window, cx);
15746        assert_eq!(editor.text(cx), "Xa\nbbb");
15747        assert_eq!(
15748            editor.selections.ranges(cx),
15749            [Point::new(1, 0)..Point::new(1, 0)]
15750        );
15751
15752        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15753            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
15754        });
15755        editor.backspace(&Default::default(), window, cx);
15756        assert_eq!(editor.text(cx), "X\nbb");
15757        assert_eq!(
15758            editor.selections.ranges(cx),
15759            [Point::new(0, 1)..Point::new(0, 1)]
15760        );
15761    });
15762}
15763
15764#[gpui::test]
15765fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
15766    init_test(cx, |_| {});
15767
15768    let markers = vec![('[', ']').into(), ('(', ')').into()];
15769    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
15770        indoc! {"
15771            [aaaa
15772            (bbbb]
15773            cccc)",
15774        },
15775        markers.clone(),
15776    );
15777    let excerpt_ranges = markers.into_iter().map(|marker| {
15778        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
15779        ExcerptRange::new(context)
15780    });
15781    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
15782    let multibuffer = cx.new(|cx| {
15783        let mut multibuffer = MultiBuffer::new(ReadWrite);
15784        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
15785        multibuffer
15786    });
15787
15788    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
15789    editor.update_in(cx, |editor, window, cx| {
15790        let (expected_text, selection_ranges) = marked_text_ranges(
15791            indoc! {"
15792                aaaa
15793                bˇbbb
15794                bˇbbˇb
15795                cccc"
15796            },
15797            true,
15798        );
15799        assert_eq!(editor.text(cx), expected_text);
15800        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15801            s.select_ranges(selection_ranges)
15802        });
15803
15804        editor.handle_input("X", window, cx);
15805
15806        let (expected_text, expected_selections) = marked_text_ranges(
15807            indoc! {"
15808                aaaa
15809                bXˇbbXb
15810                bXˇbbXˇb
15811                cccc"
15812            },
15813            false,
15814        );
15815        assert_eq!(editor.text(cx), expected_text);
15816        assert_eq!(editor.selections.ranges(cx), expected_selections);
15817
15818        editor.newline(&Newline, window, cx);
15819        let (expected_text, expected_selections) = marked_text_ranges(
15820            indoc! {"
15821                aaaa
15822                bX
15823                ˇbbX
15824                b
15825                bX
15826                ˇbbX
15827                ˇb
15828                cccc"
15829            },
15830            false,
15831        );
15832        assert_eq!(editor.text(cx), expected_text);
15833        assert_eq!(editor.selections.ranges(cx), expected_selections);
15834    });
15835}
15836
15837#[gpui::test]
15838fn test_refresh_selections(cx: &mut TestAppContext) {
15839    init_test(cx, |_| {});
15840
15841    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15842    let mut excerpt1_id = None;
15843    let multibuffer = cx.new(|cx| {
15844        let mut multibuffer = MultiBuffer::new(ReadWrite);
15845        excerpt1_id = multibuffer
15846            .push_excerpts(
15847                buffer.clone(),
15848                [
15849                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
15850                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
15851                ],
15852                cx,
15853            )
15854            .into_iter()
15855            .next();
15856        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
15857        multibuffer
15858    });
15859
15860    let editor = cx.add_window(|window, cx| {
15861        let mut editor = build_editor(multibuffer.clone(), window, cx);
15862        let snapshot = editor.snapshot(window, cx);
15863        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15864            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
15865        });
15866        editor.begin_selection(
15867            Point::new(2, 1).to_display_point(&snapshot),
15868            true,
15869            1,
15870            window,
15871            cx,
15872        );
15873        assert_eq!(
15874            editor.selections.ranges(cx),
15875            [
15876                Point::new(1, 3)..Point::new(1, 3),
15877                Point::new(2, 1)..Point::new(2, 1),
15878            ]
15879        );
15880        editor
15881    });
15882
15883    // Refreshing selections is a no-op when excerpts haven't changed.
15884    _ = editor.update(cx, |editor, window, cx| {
15885        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
15886        assert_eq!(
15887            editor.selections.ranges(cx),
15888            [
15889                Point::new(1, 3)..Point::new(1, 3),
15890                Point::new(2, 1)..Point::new(2, 1),
15891            ]
15892        );
15893    });
15894
15895    multibuffer.update(cx, |multibuffer, cx| {
15896        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
15897    });
15898    _ = editor.update(cx, |editor, window, cx| {
15899        // Removing an excerpt causes the first selection to become degenerate.
15900        assert_eq!(
15901            editor.selections.ranges(cx),
15902            [
15903                Point::new(0, 0)..Point::new(0, 0),
15904                Point::new(0, 1)..Point::new(0, 1)
15905            ]
15906        );
15907
15908        // Refreshing selections will relocate the first selection to the original buffer
15909        // location.
15910        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
15911        assert_eq!(
15912            editor.selections.ranges(cx),
15913            [
15914                Point::new(0, 1)..Point::new(0, 1),
15915                Point::new(0, 3)..Point::new(0, 3)
15916            ]
15917        );
15918        assert!(editor.selections.pending_anchor().is_some());
15919    });
15920}
15921
15922#[gpui::test]
15923fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
15924    init_test(cx, |_| {});
15925
15926    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15927    let mut excerpt1_id = None;
15928    let multibuffer = cx.new(|cx| {
15929        let mut multibuffer = MultiBuffer::new(ReadWrite);
15930        excerpt1_id = multibuffer
15931            .push_excerpts(
15932                buffer.clone(),
15933                [
15934                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
15935                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
15936                ],
15937                cx,
15938            )
15939            .into_iter()
15940            .next();
15941        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
15942        multibuffer
15943    });
15944
15945    let editor = cx.add_window(|window, cx| {
15946        let mut editor = build_editor(multibuffer.clone(), window, cx);
15947        let snapshot = editor.snapshot(window, cx);
15948        editor.begin_selection(
15949            Point::new(1, 3).to_display_point(&snapshot),
15950            false,
15951            1,
15952            window,
15953            cx,
15954        );
15955        assert_eq!(
15956            editor.selections.ranges(cx),
15957            [Point::new(1, 3)..Point::new(1, 3)]
15958        );
15959        editor
15960    });
15961
15962    multibuffer.update(cx, |multibuffer, cx| {
15963        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
15964    });
15965    _ = editor.update(cx, |editor, window, cx| {
15966        assert_eq!(
15967            editor.selections.ranges(cx),
15968            [Point::new(0, 0)..Point::new(0, 0)]
15969        );
15970
15971        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
15972        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
15973        assert_eq!(
15974            editor.selections.ranges(cx),
15975            [Point::new(0, 3)..Point::new(0, 3)]
15976        );
15977        assert!(editor.selections.pending_anchor().is_some());
15978    });
15979}
15980
15981#[gpui::test]
15982async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
15983    init_test(cx, |_| {});
15984
15985    let language = Arc::new(
15986        Language::new(
15987            LanguageConfig {
15988                brackets: BracketPairConfig {
15989                    pairs: vec![
15990                        BracketPair {
15991                            start: "{".to_string(),
15992                            end: "}".to_string(),
15993                            close: true,
15994                            surround: true,
15995                            newline: true,
15996                        },
15997                        BracketPair {
15998                            start: "/* ".to_string(),
15999                            end: " */".to_string(),
16000                            close: true,
16001                            surround: true,
16002                            newline: true,
16003                        },
16004                    ],
16005                    ..Default::default()
16006                },
16007                ..Default::default()
16008            },
16009            Some(tree_sitter_rust::LANGUAGE.into()),
16010        )
16011        .with_indents_query("")
16012        .unwrap(),
16013    );
16014
16015    let text = concat!(
16016        "{   }\n",     //
16017        "  x\n",       //
16018        "  /*   */\n", //
16019        "x\n",         //
16020        "{{} }\n",     //
16021    );
16022
16023    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
16024    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
16025    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
16026    editor
16027        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
16028        .await;
16029
16030    editor.update_in(cx, |editor, window, cx| {
16031        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16032            s.select_display_ranges([
16033                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
16034                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
16035                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
16036            ])
16037        });
16038        editor.newline(&Newline, window, cx);
16039
16040        assert_eq!(
16041            editor.buffer().read(cx).read(cx).text(),
16042            concat!(
16043                "{ \n",    // Suppress rustfmt
16044                "\n",      //
16045                "}\n",     //
16046                "  x\n",   //
16047                "  /* \n", //
16048                "  \n",    //
16049                "  */\n",  //
16050                "x\n",     //
16051                "{{} \n",  //
16052                "}\n",     //
16053            )
16054        );
16055    });
16056}
16057
16058#[gpui::test]
16059fn test_highlighted_ranges(cx: &mut TestAppContext) {
16060    init_test(cx, |_| {});
16061
16062    let editor = cx.add_window(|window, cx| {
16063        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
16064        build_editor(buffer, window, cx)
16065    });
16066
16067    _ = editor.update(cx, |editor, window, cx| {
16068        struct Type1;
16069        struct Type2;
16070
16071        let buffer = editor.buffer.read(cx).snapshot(cx);
16072
16073        let anchor_range =
16074            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
16075
16076        editor.highlight_background::<Type1>(
16077            &[
16078                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
16079                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
16080                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
16081                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
16082            ],
16083            |_| Hsla::red(),
16084            cx,
16085        );
16086        editor.highlight_background::<Type2>(
16087            &[
16088                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
16089                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
16090                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
16091                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
16092            ],
16093            |_| Hsla::green(),
16094            cx,
16095        );
16096
16097        let snapshot = editor.snapshot(window, cx);
16098        let highlighted_ranges = editor.sorted_background_highlights_in_range(
16099            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
16100            &snapshot,
16101            cx.theme(),
16102        );
16103        assert_eq!(
16104            highlighted_ranges,
16105            &[
16106                (
16107                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
16108                    Hsla::green(),
16109                ),
16110                (
16111                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
16112                    Hsla::red(),
16113                ),
16114                (
16115                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
16116                    Hsla::green(),
16117                ),
16118                (
16119                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16120                    Hsla::red(),
16121                ),
16122            ]
16123        );
16124        assert_eq!(
16125            editor.sorted_background_highlights_in_range(
16126                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
16127                &snapshot,
16128                cx.theme(),
16129            ),
16130            &[(
16131                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16132                Hsla::red(),
16133            )]
16134        );
16135    });
16136}
16137
16138#[gpui::test]
16139async fn test_following(cx: &mut TestAppContext) {
16140    init_test(cx, |_| {});
16141
16142    let fs = FakeFs::new(cx.executor());
16143    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16144
16145    let buffer = project.update(cx, |project, cx| {
16146        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, false, cx);
16147        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
16148    });
16149    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
16150    let follower = cx.update(|cx| {
16151        cx.open_window(
16152            WindowOptions {
16153                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
16154                    gpui::Point::new(px(0.), px(0.)),
16155                    gpui::Point::new(px(10.), px(80.)),
16156                ))),
16157                ..Default::default()
16158            },
16159            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
16160        )
16161        .unwrap()
16162    });
16163
16164    let is_still_following = Rc::new(RefCell::new(true));
16165    let follower_edit_event_count = Rc::new(RefCell::new(0));
16166    let pending_update = Rc::new(RefCell::new(None));
16167    let leader_entity = leader.root(cx).unwrap();
16168    let follower_entity = follower.root(cx).unwrap();
16169    _ = follower.update(cx, {
16170        let update = pending_update.clone();
16171        let is_still_following = is_still_following.clone();
16172        let follower_edit_event_count = follower_edit_event_count.clone();
16173        |_, window, cx| {
16174            cx.subscribe_in(
16175                &leader_entity,
16176                window,
16177                move |_, leader, event, window, cx| {
16178                    leader.read(cx).add_event_to_update_proto(
16179                        event,
16180                        &mut update.borrow_mut(),
16181                        window,
16182                        cx,
16183                    );
16184                },
16185            )
16186            .detach();
16187
16188            cx.subscribe_in(
16189                &follower_entity,
16190                window,
16191                move |_, _, event: &EditorEvent, _window, _cx| {
16192                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
16193                        *is_still_following.borrow_mut() = false;
16194                    }
16195
16196                    if let EditorEvent::BufferEdited = event {
16197                        *follower_edit_event_count.borrow_mut() += 1;
16198                    }
16199                },
16200            )
16201            .detach();
16202        }
16203    });
16204
16205    // Update the selections only
16206    _ = leader.update(cx, |leader, window, cx| {
16207        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16208            s.select_ranges([1..1])
16209        });
16210    });
16211    follower
16212        .update(cx, |follower, window, cx| {
16213            follower.apply_update_proto(
16214                &project,
16215                pending_update.borrow_mut().take().unwrap(),
16216                window,
16217                cx,
16218            )
16219        })
16220        .unwrap()
16221        .await
16222        .unwrap();
16223    _ = follower.update(cx, |follower, _, cx| {
16224        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
16225    });
16226    assert!(*is_still_following.borrow());
16227    assert_eq!(*follower_edit_event_count.borrow(), 0);
16228
16229    // Update the scroll position only
16230    _ = leader.update(cx, |leader, window, cx| {
16231        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16232    });
16233    follower
16234        .update(cx, |follower, window, cx| {
16235            follower.apply_update_proto(
16236                &project,
16237                pending_update.borrow_mut().take().unwrap(),
16238                window,
16239                cx,
16240            )
16241        })
16242        .unwrap()
16243        .await
16244        .unwrap();
16245    assert_eq!(
16246        follower
16247            .update(cx, |follower, _, cx| follower.scroll_position(cx))
16248            .unwrap(),
16249        gpui::Point::new(1.5, 3.5)
16250    );
16251    assert!(*is_still_following.borrow());
16252    assert_eq!(*follower_edit_event_count.borrow(), 0);
16253
16254    // Update the selections and scroll position. The follower's scroll position is updated
16255    // via autoscroll, not via the leader's exact scroll position.
16256    _ = leader.update(cx, |leader, window, cx| {
16257        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16258            s.select_ranges([0..0])
16259        });
16260        leader.request_autoscroll(Autoscroll::newest(), cx);
16261        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16262    });
16263    follower
16264        .update(cx, |follower, window, cx| {
16265            follower.apply_update_proto(
16266                &project,
16267                pending_update.borrow_mut().take().unwrap(),
16268                window,
16269                cx,
16270            )
16271        })
16272        .unwrap()
16273        .await
16274        .unwrap();
16275    _ = follower.update(cx, |follower, _, cx| {
16276        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
16277        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
16278    });
16279    assert!(*is_still_following.borrow());
16280
16281    // Creating a pending selection that precedes another selection
16282    _ = leader.update(cx, |leader, window, cx| {
16283        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16284            s.select_ranges([1..1])
16285        });
16286        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
16287    });
16288    follower
16289        .update(cx, |follower, window, cx| {
16290            follower.apply_update_proto(
16291                &project,
16292                pending_update.borrow_mut().take().unwrap(),
16293                window,
16294                cx,
16295            )
16296        })
16297        .unwrap()
16298        .await
16299        .unwrap();
16300    _ = follower.update(cx, |follower, _, cx| {
16301        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
16302    });
16303    assert!(*is_still_following.borrow());
16304
16305    // Extend the pending selection so that it surrounds another selection
16306    _ = leader.update(cx, |leader, window, cx| {
16307        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
16308    });
16309    follower
16310        .update(cx, |follower, window, cx| {
16311            follower.apply_update_proto(
16312                &project,
16313                pending_update.borrow_mut().take().unwrap(),
16314                window,
16315                cx,
16316            )
16317        })
16318        .unwrap()
16319        .await
16320        .unwrap();
16321    _ = follower.update(cx, |follower, _, cx| {
16322        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
16323    });
16324
16325    // Scrolling locally breaks the follow
16326    _ = follower.update(cx, |follower, window, cx| {
16327        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
16328        follower.set_scroll_anchor(
16329            ScrollAnchor {
16330                anchor: top_anchor,
16331                offset: gpui::Point::new(0.0, 0.5),
16332            },
16333            window,
16334            cx,
16335        );
16336    });
16337    assert!(!(*is_still_following.borrow()));
16338}
16339
16340#[gpui::test]
16341async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
16342    init_test(cx, |_| {});
16343
16344    let fs = FakeFs::new(cx.executor());
16345    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16346    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16347    let pane = workspace
16348        .update(cx, |workspace, _, _| workspace.active_pane().clone())
16349        .unwrap();
16350
16351    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16352
16353    let leader = pane.update_in(cx, |_, window, cx| {
16354        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
16355        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
16356    });
16357
16358    // Start following the editor when it has no excerpts.
16359    let mut state_message =
16360        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16361    let workspace_entity = workspace.root(cx).unwrap();
16362    let follower_1 = cx
16363        .update_window(*workspace.deref(), |_, window, cx| {
16364            Editor::from_state_proto(
16365                workspace_entity,
16366                ViewId {
16367                    creator: CollaboratorId::PeerId(PeerId::default()),
16368                    id: 0,
16369                },
16370                &mut state_message,
16371                window,
16372                cx,
16373            )
16374        })
16375        .unwrap()
16376        .unwrap()
16377        .await
16378        .unwrap();
16379
16380    let update_message = Rc::new(RefCell::new(None));
16381    follower_1.update_in(cx, {
16382        let update = update_message.clone();
16383        |_, window, cx| {
16384            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
16385                leader.read(cx).add_event_to_update_proto(
16386                    event,
16387                    &mut update.borrow_mut(),
16388                    window,
16389                    cx,
16390                );
16391            })
16392            .detach();
16393        }
16394    });
16395
16396    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
16397        (
16398            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, false, cx),
16399            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, false, cx),
16400        )
16401    });
16402
16403    // Insert some excerpts.
16404    leader.update(cx, |leader, cx| {
16405        leader.buffer.update(cx, |multibuffer, cx| {
16406            multibuffer.set_excerpts_for_path(
16407                PathKey::namespaced(1, Arc::from(Path::new("b.txt"))),
16408                buffer_1.clone(),
16409                vec![
16410                    Point::row_range(0..3),
16411                    Point::row_range(1..6),
16412                    Point::row_range(12..15),
16413                ],
16414                0,
16415                cx,
16416            );
16417            multibuffer.set_excerpts_for_path(
16418                PathKey::namespaced(1, Arc::from(Path::new("a.txt"))),
16419                buffer_2.clone(),
16420                vec![Point::row_range(0..6), Point::row_range(8..12)],
16421                0,
16422                cx,
16423            );
16424        });
16425    });
16426
16427    // Apply the update of adding the excerpts.
16428    follower_1
16429        .update_in(cx, |follower, window, cx| {
16430            follower.apply_update_proto(
16431                &project,
16432                update_message.borrow().clone().unwrap(),
16433                window,
16434                cx,
16435            )
16436        })
16437        .await
16438        .unwrap();
16439    assert_eq!(
16440        follower_1.update(cx, |editor, cx| editor.text(cx)),
16441        leader.update(cx, |editor, cx| editor.text(cx))
16442    );
16443    update_message.borrow_mut().take();
16444
16445    // Start following separately after it already has excerpts.
16446    let mut state_message =
16447        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16448    let workspace_entity = workspace.root(cx).unwrap();
16449    let follower_2 = cx
16450        .update_window(*workspace.deref(), |_, window, cx| {
16451            Editor::from_state_proto(
16452                workspace_entity,
16453                ViewId {
16454                    creator: CollaboratorId::PeerId(PeerId::default()),
16455                    id: 0,
16456                },
16457                &mut state_message,
16458                window,
16459                cx,
16460            )
16461        })
16462        .unwrap()
16463        .unwrap()
16464        .await
16465        .unwrap();
16466    assert_eq!(
16467        follower_2.update(cx, |editor, cx| editor.text(cx)),
16468        leader.update(cx, |editor, cx| editor.text(cx))
16469    );
16470
16471    // Remove some excerpts.
16472    leader.update(cx, |leader, cx| {
16473        leader.buffer.update(cx, |multibuffer, cx| {
16474            let excerpt_ids = multibuffer.excerpt_ids();
16475            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
16476            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
16477        });
16478    });
16479
16480    // Apply the update of removing the excerpts.
16481    follower_1
16482        .update_in(cx, |follower, window, cx| {
16483            follower.apply_update_proto(
16484                &project,
16485                update_message.borrow().clone().unwrap(),
16486                window,
16487                cx,
16488            )
16489        })
16490        .await
16491        .unwrap();
16492    follower_2
16493        .update_in(cx, |follower, window, cx| {
16494            follower.apply_update_proto(
16495                &project,
16496                update_message.borrow().clone().unwrap(),
16497                window,
16498                cx,
16499            )
16500        })
16501        .await
16502        .unwrap();
16503    update_message.borrow_mut().take();
16504    assert_eq!(
16505        follower_1.update(cx, |editor, cx| editor.text(cx)),
16506        leader.update(cx, |editor, cx| editor.text(cx))
16507    );
16508}
16509
16510#[gpui::test]
16511async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16512    init_test(cx, |_| {});
16513
16514    let mut cx = EditorTestContext::new(cx).await;
16515    let lsp_store =
16516        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
16517
16518    cx.set_state(indoc! {"
16519        ˇfn func(abc def: i32) -> u32 {
16520        }
16521    "});
16522
16523    cx.update(|_, cx| {
16524        lsp_store.update(cx, |lsp_store, cx| {
16525            lsp_store
16526                .update_diagnostics(
16527                    LanguageServerId(0),
16528                    lsp::PublishDiagnosticsParams {
16529                        uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
16530                        version: None,
16531                        diagnostics: vec![
16532                            lsp::Diagnostic {
16533                                range: lsp::Range::new(
16534                                    lsp::Position::new(0, 11),
16535                                    lsp::Position::new(0, 12),
16536                                ),
16537                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16538                                ..Default::default()
16539                            },
16540                            lsp::Diagnostic {
16541                                range: lsp::Range::new(
16542                                    lsp::Position::new(0, 12),
16543                                    lsp::Position::new(0, 15),
16544                                ),
16545                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16546                                ..Default::default()
16547                            },
16548                            lsp::Diagnostic {
16549                                range: lsp::Range::new(
16550                                    lsp::Position::new(0, 25),
16551                                    lsp::Position::new(0, 28),
16552                                ),
16553                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16554                                ..Default::default()
16555                            },
16556                        ],
16557                    },
16558                    None,
16559                    DiagnosticSourceKind::Pushed,
16560                    &[],
16561                    cx,
16562                )
16563                .unwrap()
16564        });
16565    });
16566
16567    executor.run_until_parked();
16568
16569    cx.update_editor(|editor, window, cx| {
16570        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16571    });
16572
16573    cx.assert_editor_state(indoc! {"
16574        fn func(abc def: i32) -> ˇu32 {
16575        }
16576    "});
16577
16578    cx.update_editor(|editor, window, cx| {
16579        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16580    });
16581
16582    cx.assert_editor_state(indoc! {"
16583        fn func(abc ˇdef: i32) -> u32 {
16584        }
16585    "});
16586
16587    cx.update_editor(|editor, window, cx| {
16588        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16589    });
16590
16591    cx.assert_editor_state(indoc! {"
16592        fn func(abcˇ def: i32) -> u32 {
16593        }
16594    "});
16595
16596    cx.update_editor(|editor, window, cx| {
16597        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16598    });
16599
16600    cx.assert_editor_state(indoc! {"
16601        fn func(abc def: i32) -> ˇu32 {
16602        }
16603    "});
16604}
16605
16606#[gpui::test]
16607async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16608    init_test(cx, |_| {});
16609
16610    let mut cx = EditorTestContext::new(cx).await;
16611
16612    let diff_base = r#"
16613        use some::mod;
16614
16615        const A: u32 = 42;
16616
16617        fn main() {
16618            println!("hello");
16619
16620            println!("world");
16621        }
16622        "#
16623    .unindent();
16624
16625    // Edits are modified, removed, modified, added
16626    cx.set_state(
16627        &r#"
16628        use some::modified;
16629
16630        ˇ
16631        fn main() {
16632            println!("hello there");
16633
16634            println!("around the");
16635            println!("world");
16636        }
16637        "#
16638        .unindent(),
16639    );
16640
16641    cx.set_head_text(&diff_base);
16642    executor.run_until_parked();
16643
16644    cx.update_editor(|editor, window, cx| {
16645        //Wrap around the bottom of the buffer
16646        for _ in 0..3 {
16647            editor.go_to_next_hunk(&GoToHunk, window, cx);
16648        }
16649    });
16650
16651    cx.assert_editor_state(
16652        &r#"
16653        ˇuse some::modified;
16654
16655
16656        fn main() {
16657            println!("hello there");
16658
16659            println!("around the");
16660            println!("world");
16661        }
16662        "#
16663        .unindent(),
16664    );
16665
16666    cx.update_editor(|editor, window, cx| {
16667        //Wrap around the top of the buffer
16668        for _ in 0..2 {
16669            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16670        }
16671    });
16672
16673    cx.assert_editor_state(
16674        &r#"
16675        use some::modified;
16676
16677
16678        fn main() {
16679        ˇ    println!("hello there");
16680
16681            println!("around the");
16682            println!("world");
16683        }
16684        "#
16685        .unindent(),
16686    );
16687
16688    cx.update_editor(|editor, window, cx| {
16689        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16690    });
16691
16692    cx.assert_editor_state(
16693        &r#"
16694        use some::modified;
16695
16696        ˇ
16697        fn main() {
16698            println!("hello there");
16699
16700            println!("around the");
16701            println!("world");
16702        }
16703        "#
16704        .unindent(),
16705    );
16706
16707    cx.update_editor(|editor, window, cx| {
16708        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16709    });
16710
16711    cx.assert_editor_state(
16712        &r#"
16713        ˇuse some::modified;
16714
16715
16716        fn main() {
16717            println!("hello there");
16718
16719            println!("around the");
16720            println!("world");
16721        }
16722        "#
16723        .unindent(),
16724    );
16725
16726    cx.update_editor(|editor, window, cx| {
16727        for _ in 0..2 {
16728            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16729        }
16730    });
16731
16732    cx.assert_editor_state(
16733        &r#"
16734        use some::modified;
16735
16736
16737        fn main() {
16738        ˇ    println!("hello there");
16739
16740            println!("around the");
16741            println!("world");
16742        }
16743        "#
16744        .unindent(),
16745    );
16746
16747    cx.update_editor(|editor, window, cx| {
16748        editor.fold(&Fold, window, cx);
16749    });
16750
16751    cx.update_editor(|editor, window, cx| {
16752        editor.go_to_next_hunk(&GoToHunk, window, cx);
16753    });
16754
16755    cx.assert_editor_state(
16756        &r#"
16757        ˇuse some::modified;
16758
16759
16760        fn main() {
16761            println!("hello there");
16762
16763            println!("around the");
16764            println!("world");
16765        }
16766        "#
16767        .unindent(),
16768    );
16769}
16770
16771#[test]
16772fn test_split_words() {
16773    fn split(text: &str) -> Vec<&str> {
16774        split_words(text).collect()
16775    }
16776
16777    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
16778    assert_eq!(split("hello_world"), &["hello_", "world"]);
16779    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
16780    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
16781    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
16782    assert_eq!(split("helloworld"), &["helloworld"]);
16783
16784    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
16785}
16786
16787#[gpui::test]
16788async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
16789    init_test(cx, |_| {});
16790
16791    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
16792    let mut assert = |before, after| {
16793        let _state_context = cx.set_state(before);
16794        cx.run_until_parked();
16795        cx.update_editor(|editor, window, cx| {
16796            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
16797        });
16798        cx.run_until_parked();
16799        cx.assert_editor_state(after);
16800    };
16801
16802    // Outside bracket jumps to outside of matching bracket
16803    assert("console.logˇ(var);", "console.log(var)ˇ;");
16804    assert("console.log(var)ˇ;", "console.logˇ(var);");
16805
16806    // Inside bracket jumps to inside of matching bracket
16807    assert("console.log(ˇvar);", "console.log(varˇ);");
16808    assert("console.log(varˇ);", "console.log(ˇvar);");
16809
16810    // When outside a bracket and inside, favor jumping to the inside bracket
16811    assert(
16812        "console.log('foo', [1, 2, 3]ˇ);",
16813        "console.log(ˇ'foo', [1, 2, 3]);",
16814    );
16815    assert(
16816        "console.log(ˇ'foo', [1, 2, 3]);",
16817        "console.log('foo', [1, 2, 3]ˇ);",
16818    );
16819
16820    // Bias forward if two options are equally likely
16821    assert(
16822        "let result = curried_fun()ˇ();",
16823        "let result = curried_fun()()ˇ;",
16824    );
16825
16826    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
16827    assert(
16828        indoc! {"
16829            function test() {
16830                console.log('test')ˇ
16831            }"},
16832        indoc! {"
16833            function test() {
16834                console.logˇ('test')
16835            }"},
16836    );
16837}
16838
16839#[gpui::test]
16840async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
16841    init_test(cx, |_| {});
16842
16843    let fs = FakeFs::new(cx.executor());
16844    fs.insert_tree(
16845        path!("/a"),
16846        json!({
16847            "main.rs": "fn main() { let a = 5; }",
16848            "other.rs": "// Test file",
16849        }),
16850    )
16851    .await;
16852    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16853
16854    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
16855    language_registry.add(Arc::new(Language::new(
16856        LanguageConfig {
16857            name: "Rust".into(),
16858            matcher: LanguageMatcher {
16859                path_suffixes: vec!["rs".to_string()],
16860                ..Default::default()
16861            },
16862            brackets: BracketPairConfig {
16863                pairs: vec![BracketPair {
16864                    start: "{".to_string(),
16865                    end: "}".to_string(),
16866                    close: true,
16867                    surround: true,
16868                    newline: true,
16869                }],
16870                disabled_scopes_by_bracket_ix: Vec::new(),
16871            },
16872            ..Default::default()
16873        },
16874        Some(tree_sitter_rust::LANGUAGE.into()),
16875    )));
16876    let mut fake_servers = language_registry.register_fake_lsp(
16877        "Rust",
16878        FakeLspAdapter {
16879            capabilities: lsp::ServerCapabilities {
16880                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
16881                    first_trigger_character: "{".to_string(),
16882                    more_trigger_character: None,
16883                }),
16884                ..Default::default()
16885            },
16886            ..Default::default()
16887        },
16888    );
16889
16890    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16891
16892    let cx = &mut VisualTestContext::from_window(*workspace, cx);
16893
16894    let worktree_id = workspace
16895        .update(cx, |workspace, _, cx| {
16896            workspace.project().update(cx, |project, cx| {
16897                project.worktrees(cx).next().unwrap().read(cx).id()
16898            })
16899        })
16900        .unwrap();
16901
16902    let buffer = project
16903        .update(cx, |project, cx| {
16904            project.open_local_buffer(path!("/a/main.rs"), cx)
16905        })
16906        .await
16907        .unwrap();
16908    let editor_handle = workspace
16909        .update(cx, |workspace, window, cx| {
16910            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
16911        })
16912        .unwrap()
16913        .await
16914        .unwrap()
16915        .downcast::<Editor>()
16916        .unwrap();
16917
16918    cx.executor().start_waiting();
16919    let fake_server = fake_servers.next().await.unwrap();
16920
16921    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
16922        |params, _| async move {
16923            assert_eq!(
16924                params.text_document_position.text_document.uri,
16925                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
16926            );
16927            assert_eq!(
16928                params.text_document_position.position,
16929                lsp::Position::new(0, 21),
16930            );
16931
16932            Ok(Some(vec![lsp::TextEdit {
16933                new_text: "]".to_string(),
16934                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
16935            }]))
16936        },
16937    );
16938
16939    editor_handle.update_in(cx, |editor, window, cx| {
16940        window.focus(&editor.focus_handle(cx));
16941        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16942            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
16943        });
16944        editor.handle_input("{", window, cx);
16945    });
16946
16947    cx.executor().run_until_parked();
16948
16949    buffer.update(cx, |buffer, _| {
16950        assert_eq!(
16951            buffer.text(),
16952            "fn main() { let a = {5}; }",
16953            "No extra braces from on type formatting should appear in the buffer"
16954        )
16955    });
16956}
16957
16958#[gpui::test(iterations = 20, seeds(31))]
16959async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
16960    init_test(cx, |_| {});
16961
16962    let mut cx = EditorLspTestContext::new_rust(
16963        lsp::ServerCapabilities {
16964            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
16965                first_trigger_character: ".".to_string(),
16966                more_trigger_character: None,
16967            }),
16968            ..Default::default()
16969        },
16970        cx,
16971    )
16972    .await;
16973
16974    cx.update_buffer(|buffer, _| {
16975        // This causes autoindent to be async.
16976        buffer.set_sync_parse_timeout(Duration::ZERO)
16977    });
16978
16979    cx.set_state("fn c() {\n    d()ˇ\n}\n");
16980    cx.simulate_keystroke("\n");
16981    cx.run_until_parked();
16982
16983    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
16984    let mut request =
16985        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
16986            let buffer_cloned = buffer_cloned.clone();
16987            async move {
16988                buffer_cloned.update(&mut cx, |buffer, _| {
16989                    assert_eq!(
16990                        buffer.text(),
16991                        "fn c() {\n    d()\n        .\n}\n",
16992                        "OnTypeFormatting should triggered after autoindent applied"
16993                    )
16994                })?;
16995
16996                Ok(Some(vec![]))
16997            }
16998        });
16999
17000    cx.simulate_keystroke(".");
17001    cx.run_until_parked();
17002
17003    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
17004    assert!(request.next().await.is_some());
17005    request.close();
17006    assert!(request.next().await.is_none());
17007}
17008
17009#[gpui::test]
17010async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
17011    init_test(cx, |_| {});
17012
17013    let fs = FakeFs::new(cx.executor());
17014    fs.insert_tree(
17015        path!("/a"),
17016        json!({
17017            "main.rs": "fn main() { let a = 5; }",
17018            "other.rs": "// Test file",
17019        }),
17020    )
17021    .await;
17022
17023    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17024
17025    let server_restarts = Arc::new(AtomicUsize::new(0));
17026    let closure_restarts = Arc::clone(&server_restarts);
17027    let language_server_name = "test language server";
17028    let language_name: LanguageName = "Rust".into();
17029
17030    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17031    language_registry.add(Arc::new(Language::new(
17032        LanguageConfig {
17033            name: language_name.clone(),
17034            matcher: LanguageMatcher {
17035                path_suffixes: vec!["rs".to_string()],
17036                ..Default::default()
17037            },
17038            ..Default::default()
17039        },
17040        Some(tree_sitter_rust::LANGUAGE.into()),
17041    )));
17042    let mut fake_servers = language_registry.register_fake_lsp(
17043        "Rust",
17044        FakeLspAdapter {
17045            name: language_server_name,
17046            initialization_options: Some(json!({
17047                "testOptionValue": true
17048            })),
17049            initializer: Some(Box::new(move |fake_server| {
17050                let task_restarts = Arc::clone(&closure_restarts);
17051                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
17052                    task_restarts.fetch_add(1, atomic::Ordering::Release);
17053                    futures::future::ready(Ok(()))
17054                });
17055            })),
17056            ..Default::default()
17057        },
17058    );
17059
17060    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17061    let _buffer = project
17062        .update(cx, |project, cx| {
17063            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
17064        })
17065        .await
17066        .unwrap();
17067    let _fake_server = fake_servers.next().await.unwrap();
17068    update_test_language_settings(cx, |language_settings| {
17069        language_settings.languages.0.insert(
17070            language_name.clone(),
17071            LanguageSettingsContent {
17072                tab_size: NonZeroU32::new(8),
17073                ..Default::default()
17074            },
17075        );
17076    });
17077    cx.executor().run_until_parked();
17078    assert_eq!(
17079        server_restarts.load(atomic::Ordering::Acquire),
17080        0,
17081        "Should not restart LSP server on an unrelated change"
17082    );
17083
17084    update_test_project_settings(cx, |project_settings| {
17085        project_settings.lsp.insert(
17086            "Some other server name".into(),
17087            LspSettings {
17088                binary: None,
17089                settings: None,
17090                initialization_options: Some(json!({
17091                    "some other init value": false
17092                })),
17093                enable_lsp_tasks: false,
17094                fetch: None,
17095            },
17096        );
17097    });
17098    cx.executor().run_until_parked();
17099    assert_eq!(
17100        server_restarts.load(atomic::Ordering::Acquire),
17101        0,
17102        "Should not restart LSP server on an unrelated LSP settings change"
17103    );
17104
17105    update_test_project_settings(cx, |project_settings| {
17106        project_settings.lsp.insert(
17107            language_server_name.into(),
17108            LspSettings {
17109                binary: None,
17110                settings: None,
17111                initialization_options: Some(json!({
17112                    "anotherInitValue": false
17113                })),
17114                enable_lsp_tasks: false,
17115                fetch: None,
17116            },
17117        );
17118    });
17119    cx.executor().run_until_parked();
17120    assert_eq!(
17121        server_restarts.load(atomic::Ordering::Acquire),
17122        1,
17123        "Should restart LSP server on a related LSP settings change"
17124    );
17125
17126    update_test_project_settings(cx, |project_settings| {
17127        project_settings.lsp.insert(
17128            language_server_name.into(),
17129            LspSettings {
17130                binary: None,
17131                settings: None,
17132                initialization_options: Some(json!({
17133                    "anotherInitValue": false
17134                })),
17135                enable_lsp_tasks: false,
17136                fetch: None,
17137            },
17138        );
17139    });
17140    cx.executor().run_until_parked();
17141    assert_eq!(
17142        server_restarts.load(atomic::Ordering::Acquire),
17143        1,
17144        "Should not restart LSP server on a related LSP settings change that is the same"
17145    );
17146
17147    update_test_project_settings(cx, |project_settings| {
17148        project_settings.lsp.insert(
17149            language_server_name.into(),
17150            LspSettings {
17151                binary: None,
17152                settings: None,
17153                initialization_options: None,
17154                enable_lsp_tasks: false,
17155                fetch: None,
17156            },
17157        );
17158    });
17159    cx.executor().run_until_parked();
17160    assert_eq!(
17161        server_restarts.load(atomic::Ordering::Acquire),
17162        2,
17163        "Should restart LSP server on another related LSP settings change"
17164    );
17165}
17166
17167#[gpui::test]
17168async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
17169    init_test(cx, |_| {});
17170
17171    let mut cx = EditorLspTestContext::new_rust(
17172        lsp::ServerCapabilities {
17173            completion_provider: Some(lsp::CompletionOptions {
17174                trigger_characters: Some(vec![".".to_string()]),
17175                resolve_provider: Some(true),
17176                ..Default::default()
17177            }),
17178            ..Default::default()
17179        },
17180        cx,
17181    )
17182    .await;
17183
17184    cx.set_state("fn main() { let a = 2ˇ; }");
17185    cx.simulate_keystroke(".");
17186    let completion_item = lsp::CompletionItem {
17187        label: "some".into(),
17188        kind: Some(lsp::CompletionItemKind::SNIPPET),
17189        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
17190        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
17191            kind: lsp::MarkupKind::Markdown,
17192            value: "```rust\nSome(2)\n```".to_string(),
17193        })),
17194        deprecated: Some(false),
17195        sort_text: Some("fffffff2".to_string()),
17196        filter_text: Some("some".to_string()),
17197        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
17198        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17199            range: lsp::Range {
17200                start: lsp::Position {
17201                    line: 0,
17202                    character: 22,
17203                },
17204                end: lsp::Position {
17205                    line: 0,
17206                    character: 22,
17207                },
17208            },
17209            new_text: "Some(2)".to_string(),
17210        })),
17211        additional_text_edits: Some(vec![lsp::TextEdit {
17212            range: lsp::Range {
17213                start: lsp::Position {
17214                    line: 0,
17215                    character: 20,
17216                },
17217                end: lsp::Position {
17218                    line: 0,
17219                    character: 22,
17220                },
17221            },
17222            new_text: "".to_string(),
17223        }]),
17224        ..Default::default()
17225    };
17226
17227    let closure_completion_item = completion_item.clone();
17228    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17229        let task_completion_item = closure_completion_item.clone();
17230        async move {
17231            Ok(Some(lsp::CompletionResponse::Array(vec![
17232                task_completion_item,
17233            ])))
17234        }
17235    });
17236
17237    request.next().await;
17238
17239    cx.condition(|editor, _| editor.context_menu_visible())
17240        .await;
17241    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
17242        editor
17243            .confirm_completion(&ConfirmCompletion::default(), window, cx)
17244            .unwrap()
17245    });
17246    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
17247
17248    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
17249        let task_completion_item = completion_item.clone();
17250        async move { Ok(task_completion_item) }
17251    })
17252    .next()
17253    .await
17254    .unwrap();
17255    apply_additional_edits.await.unwrap();
17256    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
17257}
17258
17259#[gpui::test]
17260async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
17261    init_test(cx, |_| {});
17262
17263    let mut cx = EditorLspTestContext::new_rust(
17264        lsp::ServerCapabilities {
17265            completion_provider: Some(lsp::CompletionOptions {
17266                trigger_characters: Some(vec![".".to_string()]),
17267                resolve_provider: Some(true),
17268                ..Default::default()
17269            }),
17270            ..Default::default()
17271        },
17272        cx,
17273    )
17274    .await;
17275
17276    cx.set_state("fn main() { let a = 2ˇ; }");
17277    cx.simulate_keystroke(".");
17278
17279    let item1 = lsp::CompletionItem {
17280        label: "method id()".to_string(),
17281        filter_text: Some("id".to_string()),
17282        detail: None,
17283        documentation: None,
17284        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17285            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17286            new_text: ".id".to_string(),
17287        })),
17288        ..lsp::CompletionItem::default()
17289    };
17290
17291    let item2 = lsp::CompletionItem {
17292        label: "other".to_string(),
17293        filter_text: Some("other".to_string()),
17294        detail: None,
17295        documentation: None,
17296        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17297            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17298            new_text: ".other".to_string(),
17299        })),
17300        ..lsp::CompletionItem::default()
17301    };
17302
17303    let item1 = item1.clone();
17304    cx.set_request_handler::<lsp::request::Completion, _, _>({
17305        let item1 = item1.clone();
17306        move |_, _, _| {
17307            let item1 = item1.clone();
17308            let item2 = item2.clone();
17309            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
17310        }
17311    })
17312    .next()
17313    .await;
17314
17315    cx.condition(|editor, _| editor.context_menu_visible())
17316        .await;
17317    cx.update_editor(|editor, _, _| {
17318        let context_menu = editor.context_menu.borrow_mut();
17319        let context_menu = context_menu
17320            .as_ref()
17321            .expect("Should have the context menu deployed");
17322        match context_menu {
17323            CodeContextMenu::Completions(completions_menu) => {
17324                let completions = completions_menu.completions.borrow_mut();
17325                assert_eq!(
17326                    completions
17327                        .iter()
17328                        .map(|completion| &completion.label.text)
17329                        .collect::<Vec<_>>(),
17330                    vec!["method id()", "other"]
17331                )
17332            }
17333            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17334        }
17335    });
17336
17337    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
17338        let item1 = item1.clone();
17339        move |_, item_to_resolve, _| {
17340            let item1 = item1.clone();
17341            async move {
17342                if item1 == item_to_resolve {
17343                    Ok(lsp::CompletionItem {
17344                        label: "method id()".to_string(),
17345                        filter_text: Some("id".to_string()),
17346                        detail: Some("Now resolved!".to_string()),
17347                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
17348                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17349                            range: lsp::Range::new(
17350                                lsp::Position::new(0, 22),
17351                                lsp::Position::new(0, 22),
17352                            ),
17353                            new_text: ".id".to_string(),
17354                        })),
17355                        ..lsp::CompletionItem::default()
17356                    })
17357                } else {
17358                    Ok(item_to_resolve)
17359                }
17360            }
17361        }
17362    })
17363    .next()
17364    .await
17365    .unwrap();
17366    cx.run_until_parked();
17367
17368    cx.update_editor(|editor, window, cx| {
17369        editor.context_menu_next(&Default::default(), window, cx);
17370    });
17371
17372    cx.update_editor(|editor, _, _| {
17373        let context_menu = editor.context_menu.borrow_mut();
17374        let context_menu = context_menu
17375            .as_ref()
17376            .expect("Should have the context menu deployed");
17377        match context_menu {
17378            CodeContextMenu::Completions(completions_menu) => {
17379                let completions = completions_menu.completions.borrow_mut();
17380                assert_eq!(
17381                    completions
17382                        .iter()
17383                        .map(|completion| &completion.label.text)
17384                        .collect::<Vec<_>>(),
17385                    vec!["method id() Now resolved!", "other"],
17386                    "Should update first completion label, but not second as the filter text did not match."
17387                );
17388            }
17389            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17390        }
17391    });
17392}
17393
17394#[gpui::test]
17395async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
17396    init_test(cx, |_| {});
17397    let mut cx = EditorLspTestContext::new_rust(
17398        lsp::ServerCapabilities {
17399            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
17400            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
17401            completion_provider: Some(lsp::CompletionOptions {
17402                resolve_provider: Some(true),
17403                ..Default::default()
17404            }),
17405            ..Default::default()
17406        },
17407        cx,
17408    )
17409    .await;
17410    cx.set_state(indoc! {"
17411        struct TestStruct {
17412            field: i32
17413        }
17414
17415        fn mainˇ() {
17416            let unused_var = 42;
17417            let test_struct = TestStruct { field: 42 };
17418        }
17419    "});
17420    let symbol_range = cx.lsp_range(indoc! {"
17421        struct TestStruct {
17422            field: i32
17423        }
17424
17425        «fn main»() {
17426            let unused_var = 42;
17427            let test_struct = TestStruct { field: 42 };
17428        }
17429    "});
17430    let mut hover_requests =
17431        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
17432            Ok(Some(lsp::Hover {
17433                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
17434                    kind: lsp::MarkupKind::Markdown,
17435                    value: "Function documentation".to_string(),
17436                }),
17437                range: Some(symbol_range),
17438            }))
17439        });
17440
17441    // Case 1: Test that code action menu hide hover popover
17442    cx.dispatch_action(Hover);
17443    hover_requests.next().await;
17444    cx.condition(|editor, _| editor.hover_state.visible()).await;
17445    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
17446        move |_, _, _| async move {
17447            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
17448                lsp::CodeAction {
17449                    title: "Remove unused variable".to_string(),
17450                    kind: Some(CodeActionKind::QUICKFIX),
17451                    edit: Some(lsp::WorkspaceEdit {
17452                        changes: Some(
17453                            [(
17454                                lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
17455                                vec![lsp::TextEdit {
17456                                    range: lsp::Range::new(
17457                                        lsp::Position::new(5, 4),
17458                                        lsp::Position::new(5, 27),
17459                                    ),
17460                                    new_text: "".to_string(),
17461                                }],
17462                            )]
17463                            .into_iter()
17464                            .collect(),
17465                        ),
17466                        ..Default::default()
17467                    }),
17468                    ..Default::default()
17469                },
17470            )]))
17471        },
17472    );
17473    cx.update_editor(|editor, window, cx| {
17474        editor.toggle_code_actions(
17475            &ToggleCodeActions {
17476                deployed_from: None,
17477                quick_launch: false,
17478            },
17479            window,
17480            cx,
17481        );
17482    });
17483    code_action_requests.next().await;
17484    cx.run_until_parked();
17485    cx.condition(|editor, _| editor.context_menu_visible())
17486        .await;
17487    cx.update_editor(|editor, _, _| {
17488        assert!(
17489            !editor.hover_state.visible(),
17490            "Hover popover should be hidden when code action menu is shown"
17491        );
17492        // Hide code actions
17493        editor.context_menu.take();
17494    });
17495
17496    // Case 2: Test that code completions hide hover popover
17497    cx.dispatch_action(Hover);
17498    hover_requests.next().await;
17499    cx.condition(|editor, _| editor.hover_state.visible()).await;
17500    let counter = Arc::new(AtomicUsize::new(0));
17501    let mut completion_requests =
17502        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17503            let counter = counter.clone();
17504            async move {
17505                counter.fetch_add(1, atomic::Ordering::Release);
17506                Ok(Some(lsp::CompletionResponse::Array(vec![
17507                    lsp::CompletionItem {
17508                        label: "main".into(),
17509                        kind: Some(lsp::CompletionItemKind::FUNCTION),
17510                        detail: Some("() -> ()".to_string()),
17511                        ..Default::default()
17512                    },
17513                    lsp::CompletionItem {
17514                        label: "TestStruct".into(),
17515                        kind: Some(lsp::CompletionItemKind::STRUCT),
17516                        detail: Some("struct TestStruct".to_string()),
17517                        ..Default::default()
17518                    },
17519                ])))
17520            }
17521        });
17522    cx.update_editor(|editor, window, cx| {
17523        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
17524    });
17525    completion_requests.next().await;
17526    cx.condition(|editor, _| editor.context_menu_visible())
17527        .await;
17528    cx.update_editor(|editor, _, _| {
17529        assert!(
17530            !editor.hover_state.visible(),
17531            "Hover popover should be hidden when completion menu is shown"
17532        );
17533    });
17534}
17535
17536#[gpui::test]
17537async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
17538    init_test(cx, |_| {});
17539
17540    let mut cx = EditorLspTestContext::new_rust(
17541        lsp::ServerCapabilities {
17542            completion_provider: Some(lsp::CompletionOptions {
17543                trigger_characters: Some(vec![".".to_string()]),
17544                resolve_provider: Some(true),
17545                ..Default::default()
17546            }),
17547            ..Default::default()
17548        },
17549        cx,
17550    )
17551    .await;
17552
17553    cx.set_state("fn main() { let a = 2ˇ; }");
17554    cx.simulate_keystroke(".");
17555
17556    let unresolved_item_1 = lsp::CompletionItem {
17557        label: "id".to_string(),
17558        filter_text: Some("id".to_string()),
17559        detail: None,
17560        documentation: None,
17561        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17562            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17563            new_text: ".id".to_string(),
17564        })),
17565        ..lsp::CompletionItem::default()
17566    };
17567    let resolved_item_1 = lsp::CompletionItem {
17568        additional_text_edits: Some(vec![lsp::TextEdit {
17569            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
17570            new_text: "!!".to_string(),
17571        }]),
17572        ..unresolved_item_1.clone()
17573    };
17574    let unresolved_item_2 = lsp::CompletionItem {
17575        label: "other".to_string(),
17576        filter_text: Some("other".to_string()),
17577        detail: None,
17578        documentation: None,
17579        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17580            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17581            new_text: ".other".to_string(),
17582        })),
17583        ..lsp::CompletionItem::default()
17584    };
17585    let resolved_item_2 = lsp::CompletionItem {
17586        additional_text_edits: Some(vec![lsp::TextEdit {
17587            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
17588            new_text: "??".to_string(),
17589        }]),
17590        ..unresolved_item_2.clone()
17591    };
17592
17593    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
17594    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
17595    cx.lsp
17596        .server
17597        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
17598            let unresolved_item_1 = unresolved_item_1.clone();
17599            let resolved_item_1 = resolved_item_1.clone();
17600            let unresolved_item_2 = unresolved_item_2.clone();
17601            let resolved_item_2 = resolved_item_2.clone();
17602            let resolve_requests_1 = resolve_requests_1.clone();
17603            let resolve_requests_2 = resolve_requests_2.clone();
17604            move |unresolved_request, _| {
17605                let unresolved_item_1 = unresolved_item_1.clone();
17606                let resolved_item_1 = resolved_item_1.clone();
17607                let unresolved_item_2 = unresolved_item_2.clone();
17608                let resolved_item_2 = resolved_item_2.clone();
17609                let resolve_requests_1 = resolve_requests_1.clone();
17610                let resolve_requests_2 = resolve_requests_2.clone();
17611                async move {
17612                    if unresolved_request == unresolved_item_1 {
17613                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
17614                        Ok(resolved_item_1.clone())
17615                    } else if unresolved_request == unresolved_item_2 {
17616                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
17617                        Ok(resolved_item_2.clone())
17618                    } else {
17619                        panic!("Unexpected completion item {unresolved_request:?}")
17620                    }
17621                }
17622            }
17623        })
17624        .detach();
17625
17626    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17627        let unresolved_item_1 = unresolved_item_1.clone();
17628        let unresolved_item_2 = unresolved_item_2.clone();
17629        async move {
17630            Ok(Some(lsp::CompletionResponse::Array(vec![
17631                unresolved_item_1,
17632                unresolved_item_2,
17633            ])))
17634        }
17635    })
17636    .next()
17637    .await;
17638
17639    cx.condition(|editor, _| editor.context_menu_visible())
17640        .await;
17641    cx.update_editor(|editor, _, _| {
17642        let context_menu = editor.context_menu.borrow_mut();
17643        let context_menu = context_menu
17644            .as_ref()
17645            .expect("Should have the context menu deployed");
17646        match context_menu {
17647            CodeContextMenu::Completions(completions_menu) => {
17648                let completions = completions_menu.completions.borrow_mut();
17649                assert_eq!(
17650                    completions
17651                        .iter()
17652                        .map(|completion| &completion.label.text)
17653                        .collect::<Vec<_>>(),
17654                    vec!["id", "other"]
17655                )
17656            }
17657            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17658        }
17659    });
17660    cx.run_until_parked();
17661
17662    cx.update_editor(|editor, window, cx| {
17663        editor.context_menu_next(&ContextMenuNext, window, cx);
17664    });
17665    cx.run_until_parked();
17666    cx.update_editor(|editor, window, cx| {
17667        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
17668    });
17669    cx.run_until_parked();
17670    cx.update_editor(|editor, window, cx| {
17671        editor.context_menu_next(&ContextMenuNext, window, cx);
17672    });
17673    cx.run_until_parked();
17674    cx.update_editor(|editor, window, cx| {
17675        editor
17676            .compose_completion(&ComposeCompletion::default(), window, cx)
17677            .expect("No task returned")
17678    })
17679    .await
17680    .expect("Completion failed");
17681    cx.run_until_parked();
17682
17683    cx.update_editor(|editor, _, cx| {
17684        assert_eq!(
17685            resolve_requests_1.load(atomic::Ordering::Acquire),
17686            1,
17687            "Should always resolve once despite multiple selections"
17688        );
17689        assert_eq!(
17690            resolve_requests_2.load(atomic::Ordering::Acquire),
17691            1,
17692            "Should always resolve once after multiple selections and applying the completion"
17693        );
17694        assert_eq!(
17695            editor.text(cx),
17696            "fn main() { let a = ??.other; }",
17697            "Should use resolved data when applying the completion"
17698        );
17699    });
17700}
17701
17702#[gpui::test]
17703async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
17704    init_test(cx, |_| {});
17705
17706    let item_0 = lsp::CompletionItem {
17707        label: "abs".into(),
17708        insert_text: Some("abs".into()),
17709        data: Some(json!({ "very": "special"})),
17710        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
17711        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
17712            lsp::InsertReplaceEdit {
17713                new_text: "abs".to_string(),
17714                insert: lsp::Range::default(),
17715                replace: lsp::Range::default(),
17716            },
17717        )),
17718        ..lsp::CompletionItem::default()
17719    };
17720    let items = iter::once(item_0.clone())
17721        .chain((11..51).map(|i| lsp::CompletionItem {
17722            label: format!("item_{}", i),
17723            insert_text: Some(format!("item_{}", i)),
17724            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
17725            ..lsp::CompletionItem::default()
17726        }))
17727        .collect::<Vec<_>>();
17728
17729    let default_commit_characters = vec!["?".to_string()];
17730    let default_data = json!({ "default": "data"});
17731    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
17732    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
17733    let default_edit_range = lsp::Range {
17734        start: lsp::Position {
17735            line: 0,
17736            character: 5,
17737        },
17738        end: lsp::Position {
17739            line: 0,
17740            character: 5,
17741        },
17742    };
17743
17744    let mut cx = EditorLspTestContext::new_rust(
17745        lsp::ServerCapabilities {
17746            completion_provider: Some(lsp::CompletionOptions {
17747                trigger_characters: Some(vec![".".to_string()]),
17748                resolve_provider: Some(true),
17749                ..Default::default()
17750            }),
17751            ..Default::default()
17752        },
17753        cx,
17754    )
17755    .await;
17756
17757    cx.set_state("fn main() { let a = 2ˇ; }");
17758    cx.simulate_keystroke(".");
17759
17760    let completion_data = default_data.clone();
17761    let completion_characters = default_commit_characters.clone();
17762    let completion_items = items.clone();
17763    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17764        let default_data = completion_data.clone();
17765        let default_commit_characters = completion_characters.clone();
17766        let items = completion_items.clone();
17767        async move {
17768            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
17769                items,
17770                item_defaults: Some(lsp::CompletionListItemDefaults {
17771                    data: Some(default_data.clone()),
17772                    commit_characters: Some(default_commit_characters.clone()),
17773                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
17774                        default_edit_range,
17775                    )),
17776                    insert_text_format: Some(default_insert_text_format),
17777                    insert_text_mode: Some(default_insert_text_mode),
17778                }),
17779                ..lsp::CompletionList::default()
17780            })))
17781        }
17782    })
17783    .next()
17784    .await;
17785
17786    let resolved_items = Arc::new(Mutex::new(Vec::new()));
17787    cx.lsp
17788        .server
17789        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
17790            let closure_resolved_items = resolved_items.clone();
17791            move |item_to_resolve, _| {
17792                let closure_resolved_items = closure_resolved_items.clone();
17793                async move {
17794                    closure_resolved_items.lock().push(item_to_resolve.clone());
17795                    Ok(item_to_resolve)
17796                }
17797            }
17798        })
17799        .detach();
17800
17801    cx.condition(|editor, _| editor.context_menu_visible())
17802        .await;
17803    cx.run_until_parked();
17804    cx.update_editor(|editor, _, _| {
17805        let menu = editor.context_menu.borrow_mut();
17806        match menu.as_ref().expect("should have the completions menu") {
17807            CodeContextMenu::Completions(completions_menu) => {
17808                assert_eq!(
17809                    completions_menu
17810                        .entries
17811                        .borrow()
17812                        .iter()
17813                        .map(|mat| mat.string.clone())
17814                        .collect::<Vec<String>>(),
17815                    items
17816                        .iter()
17817                        .map(|completion| completion.label.clone())
17818                        .collect::<Vec<String>>()
17819                );
17820            }
17821            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
17822        }
17823    });
17824    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
17825    // with 4 from the end.
17826    assert_eq!(
17827        *resolved_items.lock(),
17828        [&items[0..16], &items[items.len() - 4..items.len()]]
17829            .concat()
17830            .iter()
17831            .cloned()
17832            .map(|mut item| {
17833                if item.data.is_none() {
17834                    item.data = Some(default_data.clone());
17835                }
17836                item
17837            })
17838            .collect::<Vec<lsp::CompletionItem>>(),
17839        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
17840    );
17841    resolved_items.lock().clear();
17842
17843    cx.update_editor(|editor, window, cx| {
17844        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
17845    });
17846    cx.run_until_parked();
17847    // Completions that have already been resolved are skipped.
17848    assert_eq!(
17849        *resolved_items.lock(),
17850        items[items.len() - 17..items.len() - 4]
17851            .iter()
17852            .cloned()
17853            .map(|mut item| {
17854                if item.data.is_none() {
17855                    item.data = Some(default_data.clone());
17856                }
17857                item
17858            })
17859            .collect::<Vec<lsp::CompletionItem>>()
17860    );
17861    resolved_items.lock().clear();
17862}
17863
17864#[gpui::test]
17865async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
17866    init_test(cx, |_| {});
17867
17868    let mut cx = EditorLspTestContext::new(
17869        Language::new(
17870            LanguageConfig {
17871                matcher: LanguageMatcher {
17872                    path_suffixes: vec!["jsx".into()],
17873                    ..Default::default()
17874                },
17875                overrides: [(
17876                    "element".into(),
17877                    LanguageConfigOverride {
17878                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
17879                        ..Default::default()
17880                    },
17881                )]
17882                .into_iter()
17883                .collect(),
17884                ..Default::default()
17885            },
17886            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
17887        )
17888        .with_override_query("(jsx_self_closing_element) @element")
17889        .unwrap(),
17890        lsp::ServerCapabilities {
17891            completion_provider: Some(lsp::CompletionOptions {
17892                trigger_characters: Some(vec![":".to_string()]),
17893                ..Default::default()
17894            }),
17895            ..Default::default()
17896        },
17897        cx,
17898    )
17899    .await;
17900
17901    cx.lsp
17902        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
17903            Ok(Some(lsp::CompletionResponse::Array(vec![
17904                lsp::CompletionItem {
17905                    label: "bg-blue".into(),
17906                    ..Default::default()
17907                },
17908                lsp::CompletionItem {
17909                    label: "bg-red".into(),
17910                    ..Default::default()
17911                },
17912                lsp::CompletionItem {
17913                    label: "bg-yellow".into(),
17914                    ..Default::default()
17915                },
17916            ])))
17917        });
17918
17919    cx.set_state(r#"<p class="bgˇ" />"#);
17920
17921    // Trigger completion when typing a dash, because the dash is an extra
17922    // word character in the 'element' scope, which contains the cursor.
17923    cx.simulate_keystroke("-");
17924    cx.executor().run_until_parked();
17925    cx.update_editor(|editor, _, _| {
17926        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17927        {
17928            assert_eq!(
17929                completion_menu_entries(menu),
17930                &["bg-blue", "bg-red", "bg-yellow"]
17931            );
17932        } else {
17933            panic!("expected completion menu to be open");
17934        }
17935    });
17936
17937    cx.simulate_keystroke("l");
17938    cx.executor().run_until_parked();
17939    cx.update_editor(|editor, _, _| {
17940        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17941        {
17942            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
17943        } else {
17944            panic!("expected completion menu to be open");
17945        }
17946    });
17947
17948    // When filtering completions, consider the character after the '-' to
17949    // be the start of a subword.
17950    cx.set_state(r#"<p class="yelˇ" />"#);
17951    cx.simulate_keystroke("l");
17952    cx.executor().run_until_parked();
17953    cx.update_editor(|editor, _, _| {
17954        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17955        {
17956            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
17957        } else {
17958            panic!("expected completion menu to be open");
17959        }
17960    });
17961}
17962
17963fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
17964    let entries = menu.entries.borrow();
17965    entries.iter().map(|mat| mat.string.clone()).collect()
17966}
17967
17968#[gpui::test]
17969async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
17970    init_test(cx, |settings| {
17971        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
17972            Formatter::Prettier,
17973        )))
17974    });
17975
17976    let fs = FakeFs::new(cx.executor());
17977    fs.insert_file(path!("/file.ts"), Default::default()).await;
17978
17979    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
17980    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17981
17982    language_registry.add(Arc::new(Language::new(
17983        LanguageConfig {
17984            name: "TypeScript".into(),
17985            matcher: LanguageMatcher {
17986                path_suffixes: vec!["ts".to_string()],
17987                ..Default::default()
17988            },
17989            ..Default::default()
17990        },
17991        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
17992    )));
17993    update_test_language_settings(cx, |settings| {
17994        settings.defaults.prettier = Some(PrettierSettings {
17995            allowed: true,
17996            ..PrettierSettings::default()
17997        });
17998    });
17999
18000    let test_plugin = "test_plugin";
18001    let _ = language_registry.register_fake_lsp(
18002        "TypeScript",
18003        FakeLspAdapter {
18004            prettier_plugins: vec![test_plugin],
18005            ..Default::default()
18006        },
18007    );
18008
18009    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
18010    let buffer = project
18011        .update(cx, |project, cx| {
18012            project.open_local_buffer(path!("/file.ts"), cx)
18013        })
18014        .await
18015        .unwrap();
18016
18017    let buffer_text = "one\ntwo\nthree\n";
18018    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
18019    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
18020    editor.update_in(cx, |editor, window, cx| {
18021        editor.set_text(buffer_text, window, cx)
18022    });
18023
18024    editor
18025        .update_in(cx, |editor, window, cx| {
18026            editor.perform_format(
18027                project.clone(),
18028                FormatTrigger::Manual,
18029                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18030                window,
18031                cx,
18032            )
18033        })
18034        .unwrap()
18035        .await;
18036    assert_eq!(
18037        editor.update(cx, |editor, cx| editor.text(cx)),
18038        buffer_text.to_string() + prettier_format_suffix,
18039        "Test prettier formatting was not applied to the original buffer text",
18040    );
18041
18042    update_test_language_settings(cx, |settings| {
18043        settings.defaults.formatter = Some(SelectedFormatter::Auto)
18044    });
18045    let format = editor.update_in(cx, |editor, window, cx| {
18046        editor.perform_format(
18047            project.clone(),
18048            FormatTrigger::Manual,
18049            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18050            window,
18051            cx,
18052        )
18053    });
18054    format.await.unwrap();
18055    assert_eq!(
18056        editor.update(cx, |editor, cx| editor.text(cx)),
18057        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
18058        "Autoformatting (via test prettier) was not applied to the original buffer text",
18059    );
18060}
18061
18062#[gpui::test]
18063async fn test_addition_reverts(cx: &mut TestAppContext) {
18064    init_test(cx, |_| {});
18065    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18066    let base_text = indoc! {r#"
18067        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
18079    // When addition hunks are not adjacent to carets, no hunk revert is performed
18080    assert_hunk_revert(
18081        indoc! {r#"struct Row;
18082                   struct Row1;
18083                   struct Row1.1;
18084                   struct Row1.2;
18085                   struct Row2;ˇ
18086
18087                   struct Row4;
18088                   struct Row5;
18089                   struct Row6;
18090
18091                   struct Row8;
18092                   ˇstruct Row9;
18093                   struct Row9.1;
18094                   struct Row9.2;
18095                   struct Row9.3;
18096                   struct Row10;"#},
18097        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18098        indoc! {r#"struct Row;
18099                   struct Row1;
18100                   struct Row1.1;
18101                   struct Row1.2;
18102                   struct Row2;ˇ
18103
18104                   struct Row4;
18105                   struct Row5;
18106                   struct Row6;
18107
18108                   struct Row8;
18109                   ˇstruct Row9;
18110                   struct Row9.1;
18111                   struct Row9.2;
18112                   struct Row9.3;
18113                   struct Row10;"#},
18114        base_text,
18115        &mut cx,
18116    );
18117    // Same for selections
18118    assert_hunk_revert(
18119        indoc! {r#"struct Row;
18120                   struct Row1;
18121                   struct Row2;
18122                   struct Row2.1;
18123                   struct Row2.2;
18124                   «ˇ
18125                   struct Row4;
18126                   struct» Row5;
18127                   «struct Row6;
18128                   ˇ»
18129                   struct Row9.1;
18130                   struct Row9.2;
18131                   struct Row9.3;
18132                   struct Row8;
18133                   struct Row9;
18134                   struct Row10;"#},
18135        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18136        indoc! {r#"struct Row;
18137                   struct Row1;
18138                   struct Row2;
18139                   struct Row2.1;
18140                   struct Row2.2;
18141                   «ˇ
18142                   struct Row4;
18143                   struct» Row5;
18144                   «struct Row6;
18145                   ˇ»
18146                   struct Row9.1;
18147                   struct Row9.2;
18148                   struct Row9.3;
18149                   struct Row8;
18150                   struct Row9;
18151                   struct Row10;"#},
18152        base_text,
18153        &mut cx,
18154    );
18155
18156    // When carets and selections intersect the addition hunks, those are reverted.
18157    // Adjacent carets got merged.
18158    assert_hunk_revert(
18159        indoc! {r#"struct Row;
18160                   ˇ// something on the top
18161                   struct Row1;
18162                   struct Row2;
18163                   struct Roˇw3.1;
18164                   struct Row2.2;
18165                   struct Row2.3;ˇ
18166
18167                   struct Row4;
18168                   struct ˇRow5.1;
18169                   struct Row5.2;
18170                   struct «Rowˇ»5.3;
18171                   struct Row5;
18172                   struct Row6;
18173                   ˇ
18174                   struct Row9.1;
18175                   struct «Rowˇ»9.2;
18176                   struct «ˇRow»9.3;
18177                   struct Row8;
18178                   struct Row9;
18179                   «ˇ// something on bottom»
18180                   struct Row10;"#},
18181        vec![
18182            DiffHunkStatusKind::Added,
18183            DiffHunkStatusKind::Added,
18184            DiffHunkStatusKind::Added,
18185            DiffHunkStatusKind::Added,
18186            DiffHunkStatusKind::Added,
18187        ],
18188        indoc! {r#"struct Row;
18189                   ˇstruct Row1;
18190                   struct Row2;
18191                   ˇ
18192                   struct Row4;
18193                   ˇstruct Row5;
18194                   struct Row6;
18195                   ˇ
18196                   ˇstruct Row8;
18197                   struct Row9;
18198                   ˇstruct Row10;"#},
18199        base_text,
18200        &mut cx,
18201    );
18202}
18203
18204#[gpui::test]
18205async fn test_modification_reverts(cx: &mut TestAppContext) {
18206    init_test(cx, |_| {});
18207    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18208    let base_text = indoc! {r#"
18209        struct Row;
18210        struct Row1;
18211        struct Row2;
18212
18213        struct Row4;
18214        struct Row5;
18215        struct Row6;
18216
18217        struct Row8;
18218        struct Row9;
18219        struct Row10;"#};
18220
18221    // Modification hunks behave the same as the addition ones.
18222    assert_hunk_revert(
18223        indoc! {r#"struct Row;
18224                   struct Row1;
18225                   struct Row33;
18226                   ˇ
18227                   struct Row4;
18228                   struct Row5;
18229                   struct Row6;
18230                   ˇ
18231                   struct Row99;
18232                   struct Row9;
18233                   struct Row10;"#},
18234        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18235        indoc! {r#"struct Row;
18236                   struct Row1;
18237                   struct Row33;
18238                   ˇ
18239                   struct Row4;
18240                   struct Row5;
18241                   struct Row6;
18242                   ˇ
18243                   struct Row99;
18244                   struct Row9;
18245                   struct Row10;"#},
18246        base_text,
18247        &mut cx,
18248    );
18249    assert_hunk_revert(
18250        indoc! {r#"struct Row;
18251                   struct Row1;
18252                   struct Row33;
18253                   «ˇ
18254                   struct Row4;
18255                   struct» Row5;
18256                   «struct Row6;
18257                   ˇ»
18258                   struct Row99;
18259                   struct Row9;
18260                   struct Row10;"#},
18261        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18262        indoc! {r#"struct Row;
18263                   struct Row1;
18264                   struct Row33;
18265                   «ˇ
18266                   struct Row4;
18267                   struct» Row5;
18268                   «struct Row6;
18269                   ˇ»
18270                   struct Row99;
18271                   struct Row9;
18272                   struct Row10;"#},
18273        base_text,
18274        &mut cx,
18275    );
18276
18277    assert_hunk_revert(
18278        indoc! {r#"ˇstruct Row1.1;
18279                   struct Row1;
18280                   «ˇstr»uct Row22;
18281
18282                   struct ˇRow44;
18283                   struct Row5;
18284                   struct «Rˇ»ow66;ˇ
18285
18286                   «struˇ»ct Row88;
18287                   struct Row9;
18288                   struct Row1011;ˇ"#},
18289        vec![
18290            DiffHunkStatusKind::Modified,
18291            DiffHunkStatusKind::Modified,
18292            DiffHunkStatusKind::Modified,
18293            DiffHunkStatusKind::Modified,
18294            DiffHunkStatusKind::Modified,
18295            DiffHunkStatusKind::Modified,
18296        ],
18297        indoc! {r#"struct Row;
18298                   ˇstruct Row1;
18299                   struct Row2;
18300                   ˇ
18301                   struct Row4;
18302                   ˇstruct Row5;
18303                   struct Row6;
18304                   ˇ
18305                   struct Row8;
18306                   ˇstruct Row9;
18307                   struct Row10;ˇ"#},
18308        base_text,
18309        &mut cx,
18310    );
18311}
18312
18313#[gpui::test]
18314async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
18315    init_test(cx, |_| {});
18316    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18317    let base_text = indoc! {r#"
18318        one
18319
18320        two
18321        three
18322        "#};
18323
18324    cx.set_head_text(base_text);
18325    cx.set_state("\nˇ\n");
18326    cx.executor().run_until_parked();
18327    cx.update_editor(|editor, _window, cx| {
18328        editor.expand_selected_diff_hunks(cx);
18329    });
18330    cx.executor().run_until_parked();
18331    cx.update_editor(|editor, window, cx| {
18332        editor.backspace(&Default::default(), window, cx);
18333    });
18334    cx.run_until_parked();
18335    cx.assert_state_with_diff(
18336        indoc! {r#"
18337
18338        - two
18339        - threeˇ
18340        +
18341        "#}
18342        .to_string(),
18343    );
18344}
18345
18346#[gpui::test]
18347async fn test_deletion_reverts(cx: &mut TestAppContext) {
18348    init_test(cx, |_| {});
18349    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18350    let base_text = indoc! {r#"struct Row;
18351struct Row1;
18352struct Row2;
18353
18354struct Row4;
18355struct Row5;
18356struct Row6;
18357
18358struct Row8;
18359struct Row9;
18360struct Row10;"#};
18361
18362    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
18363    assert_hunk_revert(
18364        indoc! {r#"struct Row;
18365                   struct Row2;
18366
18367                   ˇstruct Row4;
18368                   struct Row5;
18369                   struct Row6;
18370                   ˇ
18371                   struct Row8;
18372                   struct Row10;"#},
18373        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18374        indoc! {r#"struct Row;
18375                   struct Row2;
18376
18377                   ˇstruct Row4;
18378                   struct Row5;
18379                   struct Row6;
18380                   ˇ
18381                   struct Row8;
18382                   struct Row10;"#},
18383        base_text,
18384        &mut cx,
18385    );
18386    assert_hunk_revert(
18387        indoc! {r#"struct Row;
18388                   struct Row2;
18389
18390                   «ˇstruct Row4;
18391                   struct» Row5;
18392                   «struct Row6;
18393                   ˇ»
18394                   struct Row8;
18395                   struct Row10;"#},
18396        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18397        indoc! {r#"struct Row;
18398                   struct Row2;
18399
18400                   «ˇstruct Row4;
18401                   struct» Row5;
18402                   «struct Row6;
18403                   ˇ»
18404                   struct Row8;
18405                   struct Row10;"#},
18406        base_text,
18407        &mut cx,
18408    );
18409
18410    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
18411    assert_hunk_revert(
18412        indoc! {r#"struct Row;
18413                   ˇstruct Row2;
18414
18415                   struct Row4;
18416                   struct Row5;
18417                   struct Row6;
18418
18419                   struct Row8;ˇ
18420                   struct Row10;"#},
18421        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18422        indoc! {r#"struct Row;
18423                   struct Row1;
18424                   ˇstruct Row2;
18425
18426                   struct Row4;
18427                   struct Row5;
18428                   struct Row6;
18429
18430                   struct Row8;ˇ
18431                   struct Row9;
18432                   struct Row10;"#},
18433        base_text,
18434        &mut cx,
18435    );
18436    assert_hunk_revert(
18437        indoc! {r#"struct Row;
18438                   struct Row2«ˇ;
18439                   struct Row4;
18440                   struct» Row5;
18441                   «struct Row6;
18442
18443                   struct Row8;ˇ»
18444                   struct Row10;"#},
18445        vec![
18446            DiffHunkStatusKind::Deleted,
18447            DiffHunkStatusKind::Deleted,
18448            DiffHunkStatusKind::Deleted,
18449        ],
18450        indoc! {r#"struct Row;
18451                   struct Row1;
18452                   struct Row2«ˇ;
18453
18454                   struct Row4;
18455                   struct» Row5;
18456                   «struct Row6;
18457
18458                   struct Row8;ˇ»
18459                   struct Row9;
18460                   struct Row10;"#},
18461        base_text,
18462        &mut cx,
18463    );
18464}
18465
18466#[gpui::test]
18467async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
18468    init_test(cx, |_| {});
18469
18470    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
18471    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
18472    let base_text_3 =
18473        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
18474
18475    let text_1 = edit_first_char_of_every_line(base_text_1);
18476    let text_2 = edit_first_char_of_every_line(base_text_2);
18477    let text_3 = edit_first_char_of_every_line(base_text_3);
18478
18479    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
18480    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
18481    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
18482
18483    let multibuffer = cx.new(|cx| {
18484        let mut multibuffer = MultiBuffer::new(ReadWrite);
18485        multibuffer.push_excerpts(
18486            buffer_1.clone(),
18487            [
18488                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18489                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18490                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18491            ],
18492            cx,
18493        );
18494        multibuffer.push_excerpts(
18495            buffer_2.clone(),
18496            [
18497                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18498                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18499                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18500            ],
18501            cx,
18502        );
18503        multibuffer.push_excerpts(
18504            buffer_3.clone(),
18505            [
18506                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18507                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18508                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18509            ],
18510            cx,
18511        );
18512        multibuffer
18513    });
18514
18515    let fs = FakeFs::new(cx.executor());
18516    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
18517    let (editor, cx) = cx
18518        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
18519    editor.update_in(cx, |editor, _window, cx| {
18520        for (buffer, diff_base) in [
18521            (buffer_1.clone(), base_text_1),
18522            (buffer_2.clone(), base_text_2),
18523            (buffer_3.clone(), base_text_3),
18524        ] {
18525            let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
18526            editor
18527                .buffer
18528                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
18529        }
18530    });
18531    cx.executor().run_until_parked();
18532
18533    editor.update_in(cx, |editor, window, cx| {
18534        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}");
18535        editor.select_all(&SelectAll, window, cx);
18536        editor.git_restore(&Default::default(), window, cx);
18537    });
18538    cx.executor().run_until_parked();
18539
18540    // When all ranges are selected, all buffer hunks are reverted.
18541    editor.update(cx, |editor, cx| {
18542        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");
18543    });
18544    buffer_1.update(cx, |buffer, _| {
18545        assert_eq!(buffer.text(), base_text_1);
18546    });
18547    buffer_2.update(cx, |buffer, _| {
18548        assert_eq!(buffer.text(), base_text_2);
18549    });
18550    buffer_3.update(cx, |buffer, _| {
18551        assert_eq!(buffer.text(), base_text_3);
18552    });
18553
18554    editor.update_in(cx, |editor, window, cx| {
18555        editor.undo(&Default::default(), window, cx);
18556    });
18557
18558    editor.update_in(cx, |editor, window, cx| {
18559        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18560            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
18561        });
18562        editor.git_restore(&Default::default(), window, cx);
18563    });
18564
18565    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
18566    // but not affect buffer_2 and its related excerpts.
18567    editor.update(cx, |editor, cx| {
18568        assert_eq!(
18569            editor.text(cx),
18570            "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}"
18571        );
18572    });
18573    buffer_1.update(cx, |buffer, _| {
18574        assert_eq!(buffer.text(), base_text_1);
18575    });
18576    buffer_2.update(cx, |buffer, _| {
18577        assert_eq!(
18578            buffer.text(),
18579            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
18580        );
18581    });
18582    buffer_3.update(cx, |buffer, _| {
18583        assert_eq!(
18584            buffer.text(),
18585            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
18586        );
18587    });
18588
18589    fn edit_first_char_of_every_line(text: &str) -> String {
18590        text.split('\n')
18591            .map(|line| format!("X{}", &line[1..]))
18592            .collect::<Vec<_>>()
18593            .join("\n")
18594    }
18595}
18596
18597#[gpui::test]
18598async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
18599    init_test(cx, |_| {});
18600
18601    let cols = 4;
18602    let rows = 10;
18603    let sample_text_1 = sample_text(rows, cols, 'a');
18604    assert_eq!(
18605        sample_text_1,
18606        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
18607    );
18608    let sample_text_2 = sample_text(rows, cols, 'l');
18609    assert_eq!(
18610        sample_text_2,
18611        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
18612    );
18613    let sample_text_3 = sample_text(rows, cols, 'v');
18614    assert_eq!(
18615        sample_text_3,
18616        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
18617    );
18618
18619    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
18620    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
18621    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
18622
18623    let multi_buffer = cx.new(|cx| {
18624        let mut multibuffer = MultiBuffer::new(ReadWrite);
18625        multibuffer.push_excerpts(
18626            buffer_1.clone(),
18627            [
18628                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18629                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18630                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18631            ],
18632            cx,
18633        );
18634        multibuffer.push_excerpts(
18635            buffer_2.clone(),
18636            [
18637                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18638                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18639                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18640            ],
18641            cx,
18642        );
18643        multibuffer.push_excerpts(
18644            buffer_3.clone(),
18645            [
18646                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18647                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18648                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18649            ],
18650            cx,
18651        );
18652        multibuffer
18653    });
18654
18655    let fs = FakeFs::new(cx.executor());
18656    fs.insert_tree(
18657        "/a",
18658        json!({
18659            "main.rs": sample_text_1,
18660            "other.rs": sample_text_2,
18661            "lib.rs": sample_text_3,
18662        }),
18663    )
18664    .await;
18665    let project = Project::test(fs, ["/a".as_ref()], cx).await;
18666    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18667    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18668    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
18669        Editor::new(
18670            EditorMode::full(),
18671            multi_buffer,
18672            Some(project.clone()),
18673            window,
18674            cx,
18675        )
18676    });
18677    let multibuffer_item_id = workspace
18678        .update(cx, |workspace, window, cx| {
18679            assert!(
18680                workspace.active_item(cx).is_none(),
18681                "active item should be None before the first item is added"
18682            );
18683            workspace.add_item_to_active_pane(
18684                Box::new(multi_buffer_editor.clone()),
18685                None,
18686                true,
18687                window,
18688                cx,
18689            );
18690            let active_item = workspace
18691                .active_item(cx)
18692                .expect("should have an active item after adding the multi buffer");
18693            assert!(
18694                !active_item.is_singleton(cx),
18695                "A multi buffer was expected to active after adding"
18696            );
18697            active_item.item_id()
18698        })
18699        .unwrap();
18700    cx.executor().run_until_parked();
18701
18702    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18703        editor.change_selections(
18704            SelectionEffects::scroll(Autoscroll::Next),
18705            window,
18706            cx,
18707            |s| s.select_ranges(Some(1..2)),
18708        );
18709        editor.open_excerpts(&OpenExcerpts, window, cx);
18710    });
18711    cx.executor().run_until_parked();
18712    let first_item_id = workspace
18713        .update(cx, |workspace, window, cx| {
18714            let active_item = workspace
18715                .active_item(cx)
18716                .expect("should have an active item after navigating into the 1st buffer");
18717            let first_item_id = active_item.item_id();
18718            assert_ne!(
18719                first_item_id, multibuffer_item_id,
18720                "Should navigate into the 1st buffer and activate it"
18721            );
18722            assert!(
18723                active_item.is_singleton(cx),
18724                "New active item should be a singleton buffer"
18725            );
18726            assert_eq!(
18727                active_item
18728                    .act_as::<Editor>(cx)
18729                    .expect("should have navigated into an editor for the 1st buffer")
18730                    .read(cx)
18731                    .text(cx),
18732                sample_text_1
18733            );
18734
18735            workspace
18736                .go_back(workspace.active_pane().downgrade(), window, cx)
18737                .detach_and_log_err(cx);
18738
18739            first_item_id
18740        })
18741        .unwrap();
18742    cx.executor().run_until_parked();
18743    workspace
18744        .update(cx, |workspace, _, cx| {
18745            let active_item = workspace
18746                .active_item(cx)
18747                .expect("should have an active item after navigating back");
18748            assert_eq!(
18749                active_item.item_id(),
18750                multibuffer_item_id,
18751                "Should navigate back to the multi buffer"
18752            );
18753            assert!(!active_item.is_singleton(cx));
18754        })
18755        .unwrap();
18756
18757    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18758        editor.change_selections(
18759            SelectionEffects::scroll(Autoscroll::Next),
18760            window,
18761            cx,
18762            |s| s.select_ranges(Some(39..40)),
18763        );
18764        editor.open_excerpts(&OpenExcerpts, window, cx);
18765    });
18766    cx.executor().run_until_parked();
18767    let second_item_id = workspace
18768        .update(cx, |workspace, window, cx| {
18769            let active_item = workspace
18770                .active_item(cx)
18771                .expect("should have an active item after navigating into the 2nd buffer");
18772            let second_item_id = active_item.item_id();
18773            assert_ne!(
18774                second_item_id, multibuffer_item_id,
18775                "Should navigate away from the multibuffer"
18776            );
18777            assert_ne!(
18778                second_item_id, first_item_id,
18779                "Should navigate into the 2nd buffer and activate it"
18780            );
18781            assert!(
18782                active_item.is_singleton(cx),
18783                "New active item should be a singleton buffer"
18784            );
18785            assert_eq!(
18786                active_item
18787                    .act_as::<Editor>(cx)
18788                    .expect("should have navigated into an editor")
18789                    .read(cx)
18790                    .text(cx),
18791                sample_text_2
18792            );
18793
18794            workspace
18795                .go_back(workspace.active_pane().downgrade(), window, cx)
18796                .detach_and_log_err(cx);
18797
18798            second_item_id
18799        })
18800        .unwrap();
18801    cx.executor().run_until_parked();
18802    workspace
18803        .update(cx, |workspace, _, cx| {
18804            let active_item = workspace
18805                .active_item(cx)
18806                .expect("should have an active item after navigating back from the 2nd buffer");
18807            assert_eq!(
18808                active_item.item_id(),
18809                multibuffer_item_id,
18810                "Should navigate back from the 2nd buffer to the multi buffer"
18811            );
18812            assert!(!active_item.is_singleton(cx));
18813        })
18814        .unwrap();
18815
18816    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18817        editor.change_selections(
18818            SelectionEffects::scroll(Autoscroll::Next),
18819            window,
18820            cx,
18821            |s| s.select_ranges(Some(70..70)),
18822        );
18823        editor.open_excerpts(&OpenExcerpts, window, cx);
18824    });
18825    cx.executor().run_until_parked();
18826    workspace
18827        .update(cx, |workspace, window, cx| {
18828            let active_item = workspace
18829                .active_item(cx)
18830                .expect("should have an active item after navigating into the 3rd buffer");
18831            let third_item_id = active_item.item_id();
18832            assert_ne!(
18833                third_item_id, multibuffer_item_id,
18834                "Should navigate into the 3rd buffer and activate it"
18835            );
18836            assert_ne!(third_item_id, first_item_id);
18837            assert_ne!(third_item_id, second_item_id);
18838            assert!(
18839                active_item.is_singleton(cx),
18840                "New active item should be a singleton buffer"
18841            );
18842            assert_eq!(
18843                active_item
18844                    .act_as::<Editor>(cx)
18845                    .expect("should have navigated into an editor")
18846                    .read(cx)
18847                    .text(cx),
18848                sample_text_3
18849            );
18850
18851            workspace
18852                .go_back(workspace.active_pane().downgrade(), window, cx)
18853                .detach_and_log_err(cx);
18854        })
18855        .unwrap();
18856    cx.executor().run_until_parked();
18857    workspace
18858        .update(cx, |workspace, _, cx| {
18859            let active_item = workspace
18860                .active_item(cx)
18861                .expect("should have an active item after navigating back from the 3rd buffer");
18862            assert_eq!(
18863                active_item.item_id(),
18864                multibuffer_item_id,
18865                "Should navigate back from the 3rd buffer to the multi buffer"
18866            );
18867            assert!(!active_item.is_singleton(cx));
18868        })
18869        .unwrap();
18870}
18871
18872#[gpui::test]
18873async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
18874    init_test(cx, |_| {});
18875
18876    let mut cx = EditorTestContext::new(cx).await;
18877
18878    let diff_base = r#"
18879        use some::mod;
18880
18881        const A: u32 = 42;
18882
18883        fn main() {
18884            println!("hello");
18885
18886            println!("world");
18887        }
18888        "#
18889    .unindent();
18890
18891    cx.set_state(
18892        &r#"
18893        use some::modified;
18894
18895        ˇ
18896        fn main() {
18897            println!("hello there");
18898
18899            println!("around the");
18900            println!("world");
18901        }
18902        "#
18903        .unindent(),
18904    );
18905
18906    cx.set_head_text(&diff_base);
18907    executor.run_until_parked();
18908
18909    cx.update_editor(|editor, window, cx| {
18910        editor.go_to_next_hunk(&GoToHunk, window, cx);
18911        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
18912    });
18913    executor.run_until_parked();
18914    cx.assert_state_with_diff(
18915        r#"
18916          use some::modified;
18917
18918
18919          fn main() {
18920        -     println!("hello");
18921        + ˇ    println!("hello there");
18922
18923              println!("around the");
18924              println!("world");
18925          }
18926        "#
18927        .unindent(),
18928    );
18929
18930    cx.update_editor(|editor, window, cx| {
18931        for _ in 0..2 {
18932            editor.go_to_next_hunk(&GoToHunk, window, cx);
18933            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
18934        }
18935    });
18936    executor.run_until_parked();
18937    cx.assert_state_with_diff(
18938        r#"
18939        - use some::mod;
18940        + ˇuse some::modified;
18941
18942
18943          fn main() {
18944        -     println!("hello");
18945        +     println!("hello there");
18946
18947        +     println!("around the");
18948              println!("world");
18949          }
18950        "#
18951        .unindent(),
18952    );
18953
18954    cx.update_editor(|editor, window, cx| {
18955        editor.go_to_next_hunk(&GoToHunk, window, cx);
18956        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
18957    });
18958    executor.run_until_parked();
18959    cx.assert_state_with_diff(
18960        r#"
18961        - use some::mod;
18962        + use some::modified;
18963
18964        - const A: u32 = 42;
18965          ˇ
18966          fn main() {
18967        -     println!("hello");
18968        +     println!("hello there");
18969
18970        +     println!("around the");
18971              println!("world");
18972          }
18973        "#
18974        .unindent(),
18975    );
18976
18977    cx.update_editor(|editor, window, cx| {
18978        editor.cancel(&Cancel, window, cx);
18979    });
18980
18981    cx.assert_state_with_diff(
18982        r#"
18983          use some::modified;
18984
18985          ˇ
18986          fn main() {
18987              println!("hello there");
18988
18989              println!("around the");
18990              println!("world");
18991          }
18992        "#
18993        .unindent(),
18994    );
18995}
18996
18997#[gpui::test]
18998async fn test_diff_base_change_with_expanded_diff_hunks(
18999    executor: BackgroundExecutor,
19000    cx: &mut TestAppContext,
19001) {
19002    init_test(cx, |_| {});
19003
19004    let mut cx = EditorTestContext::new(cx).await;
19005
19006    let diff_base = r#"
19007        use some::mod1;
19008        use some::mod2;
19009
19010        const A: u32 = 42;
19011        const B: u32 = 42;
19012        const C: u32 = 42;
19013
19014        fn main() {
19015            println!("hello");
19016
19017            println!("world");
19018        }
19019        "#
19020    .unindent();
19021
19022    cx.set_state(
19023        &r#"
19024        use some::mod2;
19025
19026        const A: u32 = 42;
19027        const C: u32 = 42;
19028
19029        fn main(ˇ) {
19030            //println!("hello");
19031
19032            println!("world");
19033            //
19034            //
19035        }
19036        "#
19037        .unindent(),
19038    );
19039
19040    cx.set_head_text(&diff_base);
19041    executor.run_until_parked();
19042
19043    cx.update_editor(|editor, window, cx| {
19044        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19045    });
19046    executor.run_until_parked();
19047    cx.assert_state_with_diff(
19048        r#"
19049        - use some::mod1;
19050          use some::mod2;
19051
19052          const A: u32 = 42;
19053        - const B: u32 = 42;
19054          const C: u32 = 42;
19055
19056          fn main(ˇ) {
19057        -     println!("hello");
19058        +     //println!("hello");
19059
19060              println!("world");
19061        +     //
19062        +     //
19063          }
19064        "#
19065        .unindent(),
19066    );
19067
19068    cx.set_head_text("new diff base!");
19069    executor.run_until_parked();
19070    cx.assert_state_with_diff(
19071        r#"
19072        - new diff base!
19073        + use some::mod2;
19074        +
19075        + const A: u32 = 42;
19076        + const C: u32 = 42;
19077        +
19078        + fn main(ˇ) {
19079        +     //println!("hello");
19080        +
19081        +     println!("world");
19082        +     //
19083        +     //
19084        + }
19085        "#
19086        .unindent(),
19087    );
19088}
19089
19090#[gpui::test]
19091async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
19092    init_test(cx, |_| {});
19093
19094    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19095    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19096    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19097    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19098    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
19099    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
19100
19101    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
19102    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
19103    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
19104
19105    let multi_buffer = cx.new(|cx| {
19106        let mut multibuffer = MultiBuffer::new(ReadWrite);
19107        multibuffer.push_excerpts(
19108            buffer_1.clone(),
19109            [
19110                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19111                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19112                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19113            ],
19114            cx,
19115        );
19116        multibuffer.push_excerpts(
19117            buffer_2.clone(),
19118            [
19119                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19120                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19121                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19122            ],
19123            cx,
19124        );
19125        multibuffer.push_excerpts(
19126            buffer_3.clone(),
19127            [
19128                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19129                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19130                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19131            ],
19132            cx,
19133        );
19134        multibuffer
19135    });
19136
19137    let editor =
19138        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19139    editor
19140        .update(cx, |editor, _window, cx| {
19141            for (buffer, diff_base) in [
19142                (buffer_1.clone(), file_1_old),
19143                (buffer_2.clone(), file_2_old),
19144                (buffer_3.clone(), file_3_old),
19145            ] {
19146                let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
19147                editor
19148                    .buffer
19149                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
19150            }
19151        })
19152        .unwrap();
19153
19154    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19155    cx.run_until_parked();
19156
19157    cx.assert_editor_state(
19158        &"
19159            ˇaaa
19160            ccc
19161            ddd
19162
19163            ggg
19164            hhh
19165
19166
19167            lll
19168            mmm
19169            NNN
19170
19171            qqq
19172            rrr
19173
19174            uuu
19175            111
19176            222
19177            333
19178
19179            666
19180            777
19181
19182            000
19183            !!!"
19184        .unindent(),
19185    );
19186
19187    cx.update_editor(|editor, window, cx| {
19188        editor.select_all(&SelectAll, window, cx);
19189        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19190    });
19191    cx.executor().run_until_parked();
19192
19193    cx.assert_state_with_diff(
19194        "
19195            «aaa
19196          - bbb
19197            ccc
19198            ddd
19199
19200            ggg
19201            hhh
19202
19203
19204            lll
19205            mmm
19206          - nnn
19207          + NNN
19208
19209            qqq
19210            rrr
19211
19212            uuu
19213            111
19214            222
19215            333
19216
19217          + 666
19218            777
19219
19220            000
19221            !!!ˇ»"
19222            .unindent(),
19223    );
19224}
19225
19226#[gpui::test]
19227async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
19228    init_test(cx, |_| {});
19229
19230    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
19231    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
19232
19233    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
19234    let multi_buffer = cx.new(|cx| {
19235        let mut multibuffer = MultiBuffer::new(ReadWrite);
19236        multibuffer.push_excerpts(
19237            buffer.clone(),
19238            [
19239                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
19240                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
19241                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
19242            ],
19243            cx,
19244        );
19245        multibuffer
19246    });
19247
19248    let editor =
19249        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19250    editor
19251        .update(cx, |editor, _window, cx| {
19252            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
19253            editor
19254                .buffer
19255                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
19256        })
19257        .unwrap();
19258
19259    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19260    cx.run_until_parked();
19261
19262    cx.update_editor(|editor, window, cx| {
19263        editor.expand_all_diff_hunks(&Default::default(), window, cx)
19264    });
19265    cx.executor().run_until_parked();
19266
19267    // When the start of a hunk coincides with the start of its excerpt,
19268    // the hunk is expanded. When the start of a hunk is earlier than
19269    // the start of its excerpt, the hunk is not expanded.
19270    cx.assert_state_with_diff(
19271        "
19272            ˇaaa
19273          - bbb
19274          + BBB
19275
19276          - ddd
19277          - eee
19278          + DDD
19279          + EEE
19280            fff
19281
19282            iii
19283        "
19284        .unindent(),
19285    );
19286}
19287
19288#[gpui::test]
19289async fn test_edits_around_expanded_insertion_hunks(
19290    executor: BackgroundExecutor,
19291    cx: &mut TestAppContext,
19292) {
19293    init_test(cx, |_| {});
19294
19295    let mut cx = EditorTestContext::new(cx).await;
19296
19297    let diff_base = r#"
19298        use some::mod1;
19299        use some::mod2;
19300
19301        const A: u32 = 42;
19302
19303        fn main() {
19304            println!("hello");
19305
19306            println!("world");
19307        }
19308        "#
19309    .unindent();
19310    executor.run_until_parked();
19311    cx.set_state(
19312        &r#"
19313        use some::mod1;
19314        use some::mod2;
19315
19316        const A: u32 = 42;
19317        const B: u32 = 42;
19318        const C: u32 = 42;
19319        ˇ
19320
19321        fn main() {
19322            println!("hello");
19323
19324            println!("world");
19325        }
19326        "#
19327        .unindent(),
19328    );
19329
19330    cx.set_head_text(&diff_base);
19331    executor.run_until_parked();
19332
19333    cx.update_editor(|editor, window, cx| {
19334        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19335    });
19336    executor.run_until_parked();
19337
19338    cx.assert_state_with_diff(
19339        r#"
19340        use some::mod1;
19341        use some::mod2;
19342
19343        const A: u32 = 42;
19344      + const B: u32 = 42;
19345      + const C: u32 = 42;
19346      + ˇ
19347
19348        fn main() {
19349            println!("hello");
19350
19351            println!("world");
19352        }
19353      "#
19354        .unindent(),
19355    );
19356
19357    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
19358    executor.run_until_parked();
19359
19360    cx.assert_state_with_diff(
19361        r#"
19362        use some::mod1;
19363        use some::mod2;
19364
19365        const A: u32 = 42;
19366      + const B: u32 = 42;
19367      + const C: u32 = 42;
19368      + const D: u32 = 42;
19369      + ˇ
19370
19371        fn main() {
19372            println!("hello");
19373
19374            println!("world");
19375        }
19376      "#
19377        .unindent(),
19378    );
19379
19380    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
19381    executor.run_until_parked();
19382
19383    cx.assert_state_with_diff(
19384        r#"
19385        use some::mod1;
19386        use some::mod2;
19387
19388        const A: u32 = 42;
19389      + const B: u32 = 42;
19390      + const C: u32 = 42;
19391      + const D: u32 = 42;
19392      + const E: u32 = 42;
19393      + ˇ
19394
19395        fn main() {
19396            println!("hello");
19397
19398            println!("world");
19399        }
19400      "#
19401        .unindent(),
19402    );
19403
19404    cx.update_editor(|editor, window, cx| {
19405        editor.delete_line(&DeleteLine, window, cx);
19406    });
19407    executor.run_until_parked();
19408
19409    cx.assert_state_with_diff(
19410        r#"
19411        use some::mod1;
19412        use some::mod2;
19413
19414        const A: u32 = 42;
19415      + const B: u32 = 42;
19416      + const C: u32 = 42;
19417      + const D: u32 = 42;
19418      + const E: u32 = 42;
19419        ˇ
19420        fn main() {
19421            println!("hello");
19422
19423            println!("world");
19424        }
19425      "#
19426        .unindent(),
19427    );
19428
19429    cx.update_editor(|editor, window, cx| {
19430        editor.move_up(&MoveUp, window, cx);
19431        editor.delete_line(&DeleteLine, window, cx);
19432        editor.move_up(&MoveUp, window, cx);
19433        editor.delete_line(&DeleteLine, window, cx);
19434        editor.move_up(&MoveUp, window, cx);
19435        editor.delete_line(&DeleteLine, window, cx);
19436    });
19437    executor.run_until_parked();
19438    cx.assert_state_with_diff(
19439        r#"
19440        use some::mod1;
19441        use some::mod2;
19442
19443        const A: u32 = 42;
19444      + const B: u32 = 42;
19445        ˇ
19446        fn main() {
19447            println!("hello");
19448
19449            println!("world");
19450        }
19451      "#
19452        .unindent(),
19453    );
19454
19455    cx.update_editor(|editor, window, cx| {
19456        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
19457        editor.delete_line(&DeleteLine, window, cx);
19458    });
19459    executor.run_until_parked();
19460    cx.assert_state_with_diff(
19461        r#"
19462        ˇ
19463        fn main() {
19464            println!("hello");
19465
19466            println!("world");
19467        }
19468      "#
19469        .unindent(),
19470    );
19471}
19472
19473#[gpui::test]
19474async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
19475    init_test(cx, |_| {});
19476
19477    let mut cx = EditorTestContext::new(cx).await;
19478    cx.set_head_text(indoc! { "
19479        one
19480        two
19481        three
19482        four
19483        five
19484        "
19485    });
19486    cx.set_state(indoc! { "
19487        one
19488        ˇthree
19489        five
19490    "});
19491    cx.run_until_parked();
19492    cx.update_editor(|editor, window, cx| {
19493        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19494    });
19495    cx.assert_state_with_diff(
19496        indoc! { "
19497        one
19498      - two
19499        ˇthree
19500      - four
19501        five
19502    "}
19503        .to_string(),
19504    );
19505    cx.update_editor(|editor, window, cx| {
19506        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19507    });
19508
19509    cx.assert_state_with_diff(
19510        indoc! { "
19511        one
19512        ˇthree
19513        five
19514    "}
19515        .to_string(),
19516    );
19517
19518    cx.set_state(indoc! { "
19519        one
19520        ˇTWO
19521        three
19522        four
19523        five
19524    "});
19525    cx.run_until_parked();
19526    cx.update_editor(|editor, window, cx| {
19527        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19528    });
19529
19530    cx.assert_state_with_diff(
19531        indoc! { "
19532            one
19533          - two
19534          + ˇTWO
19535            three
19536            four
19537            five
19538        "}
19539        .to_string(),
19540    );
19541    cx.update_editor(|editor, window, cx| {
19542        editor.move_up(&Default::default(), window, cx);
19543        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19544    });
19545    cx.assert_state_with_diff(
19546        indoc! { "
19547            one
19548            ˇTWO
19549            three
19550            four
19551            five
19552        "}
19553        .to_string(),
19554    );
19555}
19556
19557#[gpui::test]
19558async fn test_edits_around_expanded_deletion_hunks(
19559    executor: BackgroundExecutor,
19560    cx: &mut TestAppContext,
19561) {
19562    init_test(cx, |_| {});
19563
19564    let mut cx = EditorTestContext::new(cx).await;
19565
19566    let diff_base = r#"
19567        use some::mod1;
19568        use some::mod2;
19569
19570        const A: u32 = 42;
19571        const B: u32 = 42;
19572        const C: u32 = 42;
19573
19574
19575        fn main() {
19576            println!("hello");
19577
19578            println!("world");
19579        }
19580    "#
19581    .unindent();
19582    executor.run_until_parked();
19583    cx.set_state(
19584        &r#"
19585        use some::mod1;
19586        use some::mod2;
19587
19588        ˇconst B: u32 = 42;
19589        const C: u32 = 42;
19590
19591
19592        fn main() {
19593            println!("hello");
19594
19595            println!("world");
19596        }
19597        "#
19598        .unindent(),
19599    );
19600
19601    cx.set_head_text(&diff_base);
19602    executor.run_until_parked();
19603
19604    cx.update_editor(|editor, window, cx| {
19605        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19606    });
19607    executor.run_until_parked();
19608
19609    cx.assert_state_with_diff(
19610        r#"
19611        use some::mod1;
19612        use some::mod2;
19613
19614      - const A: u32 = 42;
19615        ˇconst B: u32 = 42;
19616        const C: u32 = 42;
19617
19618
19619        fn main() {
19620            println!("hello");
19621
19622            println!("world");
19623        }
19624      "#
19625        .unindent(),
19626    );
19627
19628    cx.update_editor(|editor, window, cx| {
19629        editor.delete_line(&DeleteLine, window, cx);
19630    });
19631    executor.run_until_parked();
19632    cx.assert_state_with_diff(
19633        r#"
19634        use some::mod1;
19635        use some::mod2;
19636
19637      - const A: u32 = 42;
19638      - const B: u32 = 42;
19639        ˇconst C: u32 = 42;
19640
19641
19642        fn main() {
19643            println!("hello");
19644
19645            println!("world");
19646        }
19647      "#
19648        .unindent(),
19649    );
19650
19651    cx.update_editor(|editor, window, cx| {
19652        editor.delete_line(&DeleteLine, window, cx);
19653    });
19654    executor.run_until_parked();
19655    cx.assert_state_with_diff(
19656        r#"
19657        use some::mod1;
19658        use some::mod2;
19659
19660      - const A: u32 = 42;
19661      - const B: u32 = 42;
19662      - const C: u32 = 42;
19663        ˇ
19664
19665        fn main() {
19666            println!("hello");
19667
19668            println!("world");
19669        }
19670      "#
19671        .unindent(),
19672    );
19673
19674    cx.update_editor(|editor, window, cx| {
19675        editor.handle_input("replacement", window, cx);
19676    });
19677    executor.run_until_parked();
19678    cx.assert_state_with_diff(
19679        r#"
19680        use some::mod1;
19681        use some::mod2;
19682
19683      - const A: u32 = 42;
19684      - const B: u32 = 42;
19685      - const C: u32 = 42;
19686      -
19687      + replacementˇ
19688
19689        fn main() {
19690            println!("hello");
19691
19692            println!("world");
19693        }
19694      "#
19695        .unindent(),
19696    );
19697}
19698
19699#[gpui::test]
19700async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19701    init_test(cx, |_| {});
19702
19703    let mut cx = EditorTestContext::new(cx).await;
19704
19705    let base_text = r#"
19706        one
19707        two
19708        three
19709        four
19710        five
19711    "#
19712    .unindent();
19713    executor.run_until_parked();
19714    cx.set_state(
19715        &r#"
19716        one
19717        two
19718        fˇour
19719        five
19720        "#
19721        .unindent(),
19722    );
19723
19724    cx.set_head_text(&base_text);
19725    executor.run_until_parked();
19726
19727    cx.update_editor(|editor, window, cx| {
19728        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19729    });
19730    executor.run_until_parked();
19731
19732    cx.assert_state_with_diff(
19733        r#"
19734          one
19735          two
19736        - three
19737          fˇour
19738          five
19739        "#
19740        .unindent(),
19741    );
19742
19743    cx.update_editor(|editor, window, cx| {
19744        editor.backspace(&Backspace, window, cx);
19745        editor.backspace(&Backspace, window, cx);
19746    });
19747    executor.run_until_parked();
19748    cx.assert_state_with_diff(
19749        r#"
19750          one
19751          two
19752        - threeˇ
19753        - four
19754        + our
19755          five
19756        "#
19757        .unindent(),
19758    );
19759}
19760
19761#[gpui::test]
19762async fn test_edit_after_expanded_modification_hunk(
19763    executor: BackgroundExecutor,
19764    cx: &mut TestAppContext,
19765) {
19766    init_test(cx, |_| {});
19767
19768    let mut cx = EditorTestContext::new(cx).await;
19769
19770    let diff_base = r#"
19771        use some::mod1;
19772        use some::mod2;
19773
19774        const A: u32 = 42;
19775        const B: u32 = 42;
19776        const C: u32 = 42;
19777        const D: u32 = 42;
19778
19779
19780        fn main() {
19781            println!("hello");
19782
19783            println!("world");
19784        }"#
19785    .unindent();
19786
19787    cx.set_state(
19788        &r#"
19789        use some::mod1;
19790        use some::mod2;
19791
19792        const A: u32 = 42;
19793        const B: u32 = 42;
19794        const C: u32 = 43ˇ
19795        const D: u32 = 42;
19796
19797
19798        fn main() {
19799            println!("hello");
19800
19801            println!("world");
19802        }"#
19803        .unindent(),
19804    );
19805
19806    cx.set_head_text(&diff_base);
19807    executor.run_until_parked();
19808    cx.update_editor(|editor, window, cx| {
19809        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19810    });
19811    executor.run_until_parked();
19812
19813    cx.assert_state_with_diff(
19814        r#"
19815        use some::mod1;
19816        use some::mod2;
19817
19818        const A: u32 = 42;
19819        const B: u32 = 42;
19820      - const C: u32 = 42;
19821      + const C: u32 = 43ˇ
19822        const D: u32 = 42;
19823
19824
19825        fn main() {
19826            println!("hello");
19827
19828            println!("world");
19829        }"#
19830        .unindent(),
19831    );
19832
19833    cx.update_editor(|editor, window, cx| {
19834        editor.handle_input("\nnew_line\n", window, cx);
19835    });
19836    executor.run_until_parked();
19837
19838    cx.assert_state_with_diff(
19839        r#"
19840        use some::mod1;
19841        use some::mod2;
19842
19843        const A: u32 = 42;
19844        const B: u32 = 42;
19845      - const C: u32 = 42;
19846      + const C: u32 = 43
19847      + new_line
19848      + ˇ
19849        const D: u32 = 42;
19850
19851
19852        fn main() {
19853            println!("hello");
19854
19855            println!("world");
19856        }"#
19857        .unindent(),
19858    );
19859}
19860
19861#[gpui::test]
19862async fn test_stage_and_unstage_added_file_hunk(
19863    executor: BackgroundExecutor,
19864    cx: &mut TestAppContext,
19865) {
19866    init_test(cx, |_| {});
19867
19868    let mut cx = EditorTestContext::new(cx).await;
19869    cx.update_editor(|editor, _, cx| {
19870        editor.set_expand_all_diff_hunks(cx);
19871    });
19872
19873    let working_copy = r#"
19874            ˇfn main() {
19875                println!("hello, world!");
19876            }
19877        "#
19878    .unindent();
19879
19880    cx.set_state(&working_copy);
19881    executor.run_until_parked();
19882
19883    cx.assert_state_with_diff(
19884        r#"
19885            + ˇfn main() {
19886            +     println!("hello, world!");
19887            + }
19888        "#
19889        .unindent(),
19890    );
19891    cx.assert_index_text(None);
19892
19893    cx.update_editor(|editor, window, cx| {
19894        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19895    });
19896    executor.run_until_parked();
19897    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
19898    cx.assert_state_with_diff(
19899        r#"
19900            + ˇfn main() {
19901            +     println!("hello, world!");
19902            + }
19903        "#
19904        .unindent(),
19905    );
19906
19907    cx.update_editor(|editor, window, cx| {
19908        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19909    });
19910    executor.run_until_parked();
19911    cx.assert_index_text(None);
19912}
19913
19914async fn setup_indent_guides_editor(
19915    text: &str,
19916    cx: &mut TestAppContext,
19917) -> (BufferId, EditorTestContext) {
19918    init_test(cx, |_| {});
19919
19920    let mut cx = EditorTestContext::new(cx).await;
19921
19922    let buffer_id = cx.update_editor(|editor, window, cx| {
19923        editor.set_text(text, window, cx);
19924        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
19925
19926        buffer_ids[0]
19927    });
19928
19929    (buffer_id, cx)
19930}
19931
19932fn assert_indent_guides(
19933    range: Range<u32>,
19934    expected: Vec<IndentGuide>,
19935    active_indices: Option<Vec<usize>>,
19936    cx: &mut EditorTestContext,
19937) {
19938    let indent_guides = cx.update_editor(|editor, window, cx| {
19939        let snapshot = editor.snapshot(window, cx).display_snapshot;
19940        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
19941            editor,
19942            MultiBufferRow(range.start)..MultiBufferRow(range.end),
19943            true,
19944            &snapshot,
19945            cx,
19946        );
19947
19948        indent_guides.sort_by(|a, b| {
19949            a.depth.cmp(&b.depth).then(
19950                a.start_row
19951                    .cmp(&b.start_row)
19952                    .then(a.end_row.cmp(&b.end_row)),
19953            )
19954        });
19955        indent_guides
19956    });
19957
19958    if let Some(expected) = active_indices {
19959        let active_indices = cx.update_editor(|editor, window, cx| {
19960            let snapshot = editor.snapshot(window, cx).display_snapshot;
19961            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
19962        });
19963
19964        assert_eq!(
19965            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
19966            expected,
19967            "Active indent guide indices do not match"
19968        );
19969    }
19970
19971    assert_eq!(indent_guides, expected, "Indent guides do not match");
19972}
19973
19974fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
19975    IndentGuide {
19976        buffer_id,
19977        start_row: MultiBufferRow(start_row),
19978        end_row: MultiBufferRow(end_row),
19979        depth,
19980        tab_size: 4,
19981        settings: IndentGuideSettings {
19982            enabled: true,
19983            line_width: 1,
19984            active_line_width: 1,
19985            ..Default::default()
19986        },
19987    }
19988}
19989
19990#[gpui::test]
19991async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
19992    let (buffer_id, mut cx) = setup_indent_guides_editor(
19993        &"
19994        fn main() {
19995            let a = 1;
19996        }"
19997        .unindent(),
19998        cx,
19999    )
20000    .await;
20001
20002    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20003}
20004
20005#[gpui::test]
20006async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
20007    let (buffer_id, mut cx) = setup_indent_guides_editor(
20008        &"
20009        fn main() {
20010            let a = 1;
20011            let b = 2;
20012        }"
20013        .unindent(),
20014        cx,
20015    )
20016    .await;
20017
20018    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
20019}
20020
20021#[gpui::test]
20022async fn test_indent_guide_nested(cx: &mut TestAppContext) {
20023    let (buffer_id, mut cx) = setup_indent_guides_editor(
20024        &"
20025        fn main() {
20026            let a = 1;
20027            if a == 3 {
20028                let b = 2;
20029            } else {
20030                let c = 3;
20031            }
20032        }"
20033        .unindent(),
20034        cx,
20035    )
20036    .await;
20037
20038    assert_indent_guides(
20039        0..8,
20040        vec![
20041            indent_guide(buffer_id, 1, 6, 0),
20042            indent_guide(buffer_id, 3, 3, 1),
20043            indent_guide(buffer_id, 5, 5, 1),
20044        ],
20045        None,
20046        &mut cx,
20047    );
20048}
20049
20050#[gpui::test]
20051async fn test_indent_guide_tab(cx: &mut TestAppContext) {
20052    let (buffer_id, mut cx) = setup_indent_guides_editor(
20053        &"
20054        fn main() {
20055            let a = 1;
20056                let b = 2;
20057            let c = 3;
20058        }"
20059        .unindent(),
20060        cx,
20061    )
20062    .await;
20063
20064    assert_indent_guides(
20065        0..5,
20066        vec![
20067            indent_guide(buffer_id, 1, 3, 0),
20068            indent_guide(buffer_id, 2, 2, 1),
20069        ],
20070        None,
20071        &mut cx,
20072    );
20073}
20074
20075#[gpui::test]
20076async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
20077    let (buffer_id, mut cx) = setup_indent_guides_editor(
20078        &"
20079        fn main() {
20080            let a = 1;
20081
20082            let c = 3;
20083        }"
20084        .unindent(),
20085        cx,
20086    )
20087    .await;
20088
20089    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
20090}
20091
20092#[gpui::test]
20093async fn test_indent_guide_complex(cx: &mut TestAppContext) {
20094    let (buffer_id, mut cx) = setup_indent_guides_editor(
20095        &"
20096        fn main() {
20097            let a = 1;
20098
20099            let c = 3;
20100
20101            if a == 3 {
20102                let b = 2;
20103            } else {
20104                let c = 3;
20105            }
20106        }"
20107        .unindent(),
20108        cx,
20109    )
20110    .await;
20111
20112    assert_indent_guides(
20113        0..11,
20114        vec![
20115            indent_guide(buffer_id, 1, 9, 0),
20116            indent_guide(buffer_id, 6, 6, 1),
20117            indent_guide(buffer_id, 8, 8, 1),
20118        ],
20119        None,
20120        &mut cx,
20121    );
20122}
20123
20124#[gpui::test]
20125async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
20126    let (buffer_id, mut cx) = setup_indent_guides_editor(
20127        &"
20128        fn main() {
20129            let a = 1;
20130
20131            let c = 3;
20132
20133            if a == 3 {
20134                let b = 2;
20135            } else {
20136                let c = 3;
20137            }
20138        }"
20139        .unindent(),
20140        cx,
20141    )
20142    .await;
20143
20144    assert_indent_guides(
20145        1..11,
20146        vec![
20147            indent_guide(buffer_id, 1, 9, 0),
20148            indent_guide(buffer_id, 6, 6, 1),
20149            indent_guide(buffer_id, 8, 8, 1),
20150        ],
20151        None,
20152        &mut cx,
20153    );
20154}
20155
20156#[gpui::test]
20157async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
20158    let (buffer_id, mut cx) = setup_indent_guides_editor(
20159        &"
20160        fn main() {
20161            let a = 1;
20162
20163            let c = 3;
20164
20165            if a == 3 {
20166                let b = 2;
20167            } else {
20168                let c = 3;
20169            }
20170        }"
20171        .unindent(),
20172        cx,
20173    )
20174    .await;
20175
20176    assert_indent_guides(
20177        1..10,
20178        vec![
20179            indent_guide(buffer_id, 1, 9, 0),
20180            indent_guide(buffer_id, 6, 6, 1),
20181            indent_guide(buffer_id, 8, 8, 1),
20182        ],
20183        None,
20184        &mut cx,
20185    );
20186}
20187
20188#[gpui::test]
20189async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
20190    let (buffer_id, mut cx) = setup_indent_guides_editor(
20191        &"
20192        fn main() {
20193            if a {
20194                b(
20195                    c,
20196                    d,
20197                )
20198            } else {
20199                e(
20200                    f
20201                )
20202            }
20203        }"
20204        .unindent(),
20205        cx,
20206    )
20207    .await;
20208
20209    assert_indent_guides(
20210        0..11,
20211        vec![
20212            indent_guide(buffer_id, 1, 10, 0),
20213            indent_guide(buffer_id, 2, 5, 1),
20214            indent_guide(buffer_id, 7, 9, 1),
20215            indent_guide(buffer_id, 3, 4, 2),
20216            indent_guide(buffer_id, 8, 8, 2),
20217        ],
20218        None,
20219        &mut cx,
20220    );
20221
20222    cx.update_editor(|editor, window, cx| {
20223        editor.fold_at(MultiBufferRow(2), window, cx);
20224        assert_eq!(
20225            editor.display_text(cx),
20226            "
20227            fn main() {
20228                if a {
20229                    b(⋯
20230                    )
20231                } else {
20232                    e(
20233                        f
20234                    )
20235                }
20236            }"
20237            .unindent()
20238        );
20239    });
20240
20241    assert_indent_guides(
20242        0..11,
20243        vec![
20244            indent_guide(buffer_id, 1, 10, 0),
20245            indent_guide(buffer_id, 2, 5, 1),
20246            indent_guide(buffer_id, 7, 9, 1),
20247            indent_guide(buffer_id, 8, 8, 2),
20248        ],
20249        None,
20250        &mut cx,
20251    );
20252}
20253
20254#[gpui::test]
20255async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
20256    let (buffer_id, mut cx) = setup_indent_guides_editor(
20257        &"
20258        block1
20259            block2
20260                block3
20261                    block4
20262            block2
20263        block1
20264        block1"
20265            .unindent(),
20266        cx,
20267    )
20268    .await;
20269
20270    assert_indent_guides(
20271        1..10,
20272        vec![
20273            indent_guide(buffer_id, 1, 4, 0),
20274            indent_guide(buffer_id, 2, 3, 1),
20275            indent_guide(buffer_id, 3, 3, 2),
20276        ],
20277        None,
20278        &mut cx,
20279    );
20280}
20281
20282#[gpui::test]
20283async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
20284    let (buffer_id, mut cx) = setup_indent_guides_editor(
20285        &"
20286        block1
20287            block2
20288                block3
20289
20290        block1
20291        block1"
20292            .unindent(),
20293        cx,
20294    )
20295    .await;
20296
20297    assert_indent_guides(
20298        0..6,
20299        vec![
20300            indent_guide(buffer_id, 1, 2, 0),
20301            indent_guide(buffer_id, 2, 2, 1),
20302        ],
20303        None,
20304        &mut cx,
20305    );
20306}
20307
20308#[gpui::test]
20309async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
20310    let (buffer_id, mut cx) = setup_indent_guides_editor(
20311        &"
20312        function component() {
20313        \treturn (
20314        \t\t\t
20315        \t\t<div>
20316        \t\t\t<abc></abc>
20317        \t\t</div>
20318        \t)
20319        }"
20320        .unindent(),
20321        cx,
20322    )
20323    .await;
20324
20325    assert_indent_guides(
20326        0..8,
20327        vec![
20328            indent_guide(buffer_id, 1, 6, 0),
20329            indent_guide(buffer_id, 2, 5, 1),
20330            indent_guide(buffer_id, 4, 4, 2),
20331        ],
20332        None,
20333        &mut cx,
20334    );
20335}
20336
20337#[gpui::test]
20338async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
20339    let (buffer_id, mut cx) = setup_indent_guides_editor(
20340        &"
20341        function component() {
20342        \treturn (
20343        \t
20344        \t\t<div>
20345        \t\t\t<abc></abc>
20346        \t\t</div>
20347        \t)
20348        }"
20349        .unindent(),
20350        cx,
20351    )
20352    .await;
20353
20354    assert_indent_guides(
20355        0..8,
20356        vec![
20357            indent_guide(buffer_id, 1, 6, 0),
20358            indent_guide(buffer_id, 2, 5, 1),
20359            indent_guide(buffer_id, 4, 4, 2),
20360        ],
20361        None,
20362        &mut cx,
20363    );
20364}
20365
20366#[gpui::test]
20367async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
20368    let (buffer_id, mut cx) = setup_indent_guides_editor(
20369        &"
20370        block1
20371
20372
20373
20374            block2
20375        "
20376        .unindent(),
20377        cx,
20378    )
20379    .await;
20380
20381    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20382}
20383
20384#[gpui::test]
20385async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
20386    let (buffer_id, mut cx) = setup_indent_guides_editor(
20387        &"
20388        def a:
20389        \tb = 3
20390        \tif True:
20391        \t\tc = 4
20392        \t\td = 5
20393        \tprint(b)
20394        "
20395        .unindent(),
20396        cx,
20397    )
20398    .await;
20399
20400    assert_indent_guides(
20401        0..6,
20402        vec![
20403            indent_guide(buffer_id, 1, 5, 0),
20404            indent_guide(buffer_id, 3, 4, 1),
20405        ],
20406        None,
20407        &mut cx,
20408    );
20409}
20410
20411#[gpui::test]
20412async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
20413    let (buffer_id, mut cx) = setup_indent_guides_editor(
20414        &"
20415    fn main() {
20416        let a = 1;
20417    }"
20418        .unindent(),
20419        cx,
20420    )
20421    .await;
20422
20423    cx.update_editor(|editor, window, cx| {
20424        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20425            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20426        });
20427    });
20428
20429    assert_indent_guides(
20430        0..3,
20431        vec![indent_guide(buffer_id, 1, 1, 0)],
20432        Some(vec![0]),
20433        &mut cx,
20434    );
20435}
20436
20437#[gpui::test]
20438async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
20439    let (buffer_id, mut cx) = setup_indent_guides_editor(
20440        &"
20441    fn main() {
20442        if 1 == 2 {
20443            let a = 1;
20444        }
20445    }"
20446        .unindent(),
20447        cx,
20448    )
20449    .await;
20450
20451    cx.update_editor(|editor, window, cx| {
20452        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20453            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20454        });
20455    });
20456
20457    assert_indent_guides(
20458        0..4,
20459        vec![
20460            indent_guide(buffer_id, 1, 3, 0),
20461            indent_guide(buffer_id, 2, 2, 1),
20462        ],
20463        Some(vec![1]),
20464        &mut cx,
20465    );
20466
20467    cx.update_editor(|editor, window, cx| {
20468        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20469            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20470        });
20471    });
20472
20473    assert_indent_guides(
20474        0..4,
20475        vec![
20476            indent_guide(buffer_id, 1, 3, 0),
20477            indent_guide(buffer_id, 2, 2, 1),
20478        ],
20479        Some(vec![1]),
20480        &mut cx,
20481    );
20482
20483    cx.update_editor(|editor, window, cx| {
20484        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20485            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
20486        });
20487    });
20488
20489    assert_indent_guides(
20490        0..4,
20491        vec![
20492            indent_guide(buffer_id, 1, 3, 0),
20493            indent_guide(buffer_id, 2, 2, 1),
20494        ],
20495        Some(vec![0]),
20496        &mut cx,
20497    );
20498}
20499
20500#[gpui::test]
20501async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
20502    let (buffer_id, mut cx) = setup_indent_guides_editor(
20503        &"
20504    fn main() {
20505        let a = 1;
20506
20507        let b = 2;
20508    }"
20509        .unindent(),
20510        cx,
20511    )
20512    .await;
20513
20514    cx.update_editor(|editor, window, cx| {
20515        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20516            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20517        });
20518    });
20519
20520    assert_indent_guides(
20521        0..5,
20522        vec![indent_guide(buffer_id, 1, 3, 0)],
20523        Some(vec![0]),
20524        &mut cx,
20525    );
20526}
20527
20528#[gpui::test]
20529async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
20530    let (buffer_id, mut cx) = setup_indent_guides_editor(
20531        &"
20532    def m:
20533        a = 1
20534        pass"
20535            .unindent(),
20536        cx,
20537    )
20538    .await;
20539
20540    cx.update_editor(|editor, window, cx| {
20541        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20542            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20543        });
20544    });
20545
20546    assert_indent_guides(
20547        0..3,
20548        vec![indent_guide(buffer_id, 1, 2, 0)],
20549        Some(vec![0]),
20550        &mut cx,
20551    );
20552}
20553
20554#[gpui::test]
20555async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
20556    init_test(cx, |_| {});
20557    let mut cx = EditorTestContext::new(cx).await;
20558    let text = indoc! {
20559        "
20560        impl A {
20561            fn b() {
20562                0;
20563                3;
20564                5;
20565                6;
20566                7;
20567            }
20568        }
20569        "
20570    };
20571    let base_text = indoc! {
20572        "
20573        impl A {
20574            fn b() {
20575                0;
20576                1;
20577                2;
20578                3;
20579                4;
20580            }
20581            fn c() {
20582                5;
20583                6;
20584                7;
20585            }
20586        }
20587        "
20588    };
20589
20590    cx.update_editor(|editor, window, cx| {
20591        editor.set_text(text, window, cx);
20592
20593        editor.buffer().update(cx, |multibuffer, cx| {
20594            let buffer = multibuffer.as_singleton().unwrap();
20595            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
20596
20597            multibuffer.set_all_diff_hunks_expanded(cx);
20598            multibuffer.add_diff(diff, cx);
20599
20600            buffer.read(cx).remote_id()
20601        })
20602    });
20603    cx.run_until_parked();
20604
20605    cx.assert_state_with_diff(
20606        indoc! { "
20607          impl A {
20608              fn b() {
20609                  0;
20610        -         1;
20611        -         2;
20612                  3;
20613        -         4;
20614        -     }
20615        -     fn c() {
20616                  5;
20617                  6;
20618                  7;
20619              }
20620          }
20621          ˇ"
20622        }
20623        .to_string(),
20624    );
20625
20626    let mut actual_guides = cx.update_editor(|editor, window, cx| {
20627        editor
20628            .snapshot(window, cx)
20629            .buffer_snapshot
20630            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
20631            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
20632            .collect::<Vec<_>>()
20633    });
20634    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
20635    assert_eq!(
20636        actual_guides,
20637        vec![
20638            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
20639            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
20640            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
20641        ]
20642    );
20643}
20644
20645#[gpui::test]
20646async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
20647    init_test(cx, |_| {});
20648    let mut cx = EditorTestContext::new(cx).await;
20649
20650    let diff_base = r#"
20651        a
20652        b
20653        c
20654        "#
20655    .unindent();
20656
20657    cx.set_state(
20658        &r#"
20659        ˇA
20660        b
20661        C
20662        "#
20663        .unindent(),
20664    );
20665    cx.set_head_text(&diff_base);
20666    cx.update_editor(|editor, window, cx| {
20667        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20668    });
20669    executor.run_until_parked();
20670
20671    let both_hunks_expanded = r#"
20672        - a
20673        + ˇA
20674          b
20675        - c
20676        + C
20677        "#
20678    .unindent();
20679
20680    cx.assert_state_with_diff(both_hunks_expanded.clone());
20681
20682    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20683        let snapshot = editor.snapshot(window, cx);
20684        let hunks = editor
20685            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20686            .collect::<Vec<_>>();
20687        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20688        let buffer_id = hunks[0].buffer_id;
20689        hunks
20690            .into_iter()
20691            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20692            .collect::<Vec<_>>()
20693    });
20694    assert_eq!(hunk_ranges.len(), 2);
20695
20696    cx.update_editor(|editor, _, cx| {
20697        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20698    });
20699    executor.run_until_parked();
20700
20701    let second_hunk_expanded = r#"
20702          ˇA
20703          b
20704        - c
20705        + C
20706        "#
20707    .unindent();
20708
20709    cx.assert_state_with_diff(second_hunk_expanded);
20710
20711    cx.update_editor(|editor, _, cx| {
20712        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20713    });
20714    executor.run_until_parked();
20715
20716    cx.assert_state_with_diff(both_hunks_expanded.clone());
20717
20718    cx.update_editor(|editor, _, cx| {
20719        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20720    });
20721    executor.run_until_parked();
20722
20723    let first_hunk_expanded = r#"
20724        - a
20725        + ˇA
20726          b
20727          C
20728        "#
20729    .unindent();
20730
20731    cx.assert_state_with_diff(first_hunk_expanded);
20732
20733    cx.update_editor(|editor, _, cx| {
20734        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20735    });
20736    executor.run_until_parked();
20737
20738    cx.assert_state_with_diff(both_hunks_expanded);
20739
20740    cx.set_state(
20741        &r#"
20742        ˇA
20743        b
20744        "#
20745        .unindent(),
20746    );
20747    cx.run_until_parked();
20748
20749    // TODO this cursor position seems bad
20750    cx.assert_state_with_diff(
20751        r#"
20752        - ˇa
20753        + A
20754          b
20755        "#
20756        .unindent(),
20757    );
20758
20759    cx.update_editor(|editor, window, cx| {
20760        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20761    });
20762
20763    cx.assert_state_with_diff(
20764        r#"
20765            - ˇa
20766            + A
20767              b
20768            - c
20769            "#
20770        .unindent(),
20771    );
20772
20773    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20774        let snapshot = editor.snapshot(window, cx);
20775        let hunks = editor
20776            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20777            .collect::<Vec<_>>();
20778        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20779        let buffer_id = hunks[0].buffer_id;
20780        hunks
20781            .into_iter()
20782            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20783            .collect::<Vec<_>>()
20784    });
20785    assert_eq!(hunk_ranges.len(), 2);
20786
20787    cx.update_editor(|editor, _, cx| {
20788        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20789    });
20790    executor.run_until_parked();
20791
20792    cx.assert_state_with_diff(
20793        r#"
20794        - ˇa
20795        + A
20796          b
20797        "#
20798        .unindent(),
20799    );
20800}
20801
20802#[gpui::test]
20803async fn test_toggle_deletion_hunk_at_start_of_file(
20804    executor: BackgroundExecutor,
20805    cx: &mut TestAppContext,
20806) {
20807    init_test(cx, |_| {});
20808    let mut cx = EditorTestContext::new(cx).await;
20809
20810    let diff_base = r#"
20811        a
20812        b
20813        c
20814        "#
20815    .unindent();
20816
20817    cx.set_state(
20818        &r#"
20819        ˇb
20820        c
20821        "#
20822        .unindent(),
20823    );
20824    cx.set_head_text(&diff_base);
20825    cx.update_editor(|editor, window, cx| {
20826        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20827    });
20828    executor.run_until_parked();
20829
20830    let hunk_expanded = r#"
20831        - a
20832          ˇb
20833          c
20834        "#
20835    .unindent();
20836
20837    cx.assert_state_with_diff(hunk_expanded.clone());
20838
20839    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20840        let snapshot = editor.snapshot(window, cx);
20841        let hunks = editor
20842            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20843            .collect::<Vec<_>>();
20844        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20845        let buffer_id = hunks[0].buffer_id;
20846        hunks
20847            .into_iter()
20848            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20849            .collect::<Vec<_>>()
20850    });
20851    assert_eq!(hunk_ranges.len(), 1);
20852
20853    cx.update_editor(|editor, _, cx| {
20854        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20855    });
20856    executor.run_until_parked();
20857
20858    let hunk_collapsed = r#"
20859          ˇb
20860          c
20861        "#
20862    .unindent();
20863
20864    cx.assert_state_with_diff(hunk_collapsed);
20865
20866    cx.update_editor(|editor, _, cx| {
20867        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20868    });
20869    executor.run_until_parked();
20870
20871    cx.assert_state_with_diff(hunk_expanded);
20872}
20873
20874#[gpui::test]
20875async fn test_display_diff_hunks(cx: &mut TestAppContext) {
20876    init_test(cx, |_| {});
20877
20878    let fs = FakeFs::new(cx.executor());
20879    fs.insert_tree(
20880        path!("/test"),
20881        json!({
20882            ".git": {},
20883            "file-1": "ONE\n",
20884            "file-2": "TWO\n",
20885            "file-3": "THREE\n",
20886        }),
20887    )
20888    .await;
20889
20890    fs.set_head_for_repo(
20891        path!("/test/.git").as_ref(),
20892        &[
20893            ("file-1".into(), "one\n".into()),
20894            ("file-2".into(), "two\n".into()),
20895            ("file-3".into(), "three\n".into()),
20896        ],
20897        "deadbeef",
20898    );
20899
20900    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
20901    let mut buffers = vec![];
20902    for i in 1..=3 {
20903        let buffer = project
20904            .update(cx, |project, cx| {
20905                let path = format!(path!("/test/file-{}"), i);
20906                project.open_local_buffer(path, cx)
20907            })
20908            .await
20909            .unwrap();
20910        buffers.push(buffer);
20911    }
20912
20913    let multibuffer = cx.new(|cx| {
20914        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
20915        multibuffer.set_all_diff_hunks_expanded(cx);
20916        for buffer in &buffers {
20917            let snapshot = buffer.read(cx).snapshot();
20918            multibuffer.set_excerpts_for_path(
20919                PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
20920                buffer.clone(),
20921                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
20922                2,
20923                cx,
20924            );
20925        }
20926        multibuffer
20927    });
20928
20929    let editor = cx.add_window(|window, cx| {
20930        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
20931    });
20932    cx.run_until_parked();
20933
20934    let snapshot = editor
20935        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
20936        .unwrap();
20937    let hunks = snapshot
20938        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
20939        .map(|hunk| match hunk {
20940            DisplayDiffHunk::Unfolded {
20941                display_row_range, ..
20942            } => display_row_range,
20943            DisplayDiffHunk::Folded { .. } => unreachable!(),
20944        })
20945        .collect::<Vec<_>>();
20946    assert_eq!(
20947        hunks,
20948        [
20949            DisplayRow(2)..DisplayRow(4),
20950            DisplayRow(7)..DisplayRow(9),
20951            DisplayRow(12)..DisplayRow(14),
20952        ]
20953    );
20954}
20955
20956#[gpui::test]
20957async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
20958    init_test(cx, |_| {});
20959
20960    let mut cx = EditorTestContext::new(cx).await;
20961    cx.set_head_text(indoc! { "
20962        one
20963        two
20964        three
20965        four
20966        five
20967        "
20968    });
20969    cx.set_index_text(indoc! { "
20970        one
20971        two
20972        three
20973        four
20974        five
20975        "
20976    });
20977    cx.set_state(indoc! {"
20978        one
20979        TWO
20980        ˇTHREE
20981        FOUR
20982        five
20983    "});
20984    cx.run_until_parked();
20985    cx.update_editor(|editor, window, cx| {
20986        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20987    });
20988    cx.run_until_parked();
20989    cx.assert_index_text(Some(indoc! {"
20990        one
20991        TWO
20992        THREE
20993        FOUR
20994        five
20995    "}));
20996    cx.set_state(indoc! { "
20997        one
20998        TWO
20999        ˇTHREE-HUNDRED
21000        FOUR
21001        five
21002    "});
21003    cx.run_until_parked();
21004    cx.update_editor(|editor, window, cx| {
21005        let snapshot = editor.snapshot(window, cx);
21006        let hunks = editor
21007            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
21008            .collect::<Vec<_>>();
21009        assert_eq!(hunks.len(), 1);
21010        assert_eq!(
21011            hunks[0].status(),
21012            DiffHunkStatus {
21013                kind: DiffHunkStatusKind::Modified,
21014                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
21015            }
21016        );
21017
21018        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21019    });
21020    cx.run_until_parked();
21021    cx.assert_index_text(Some(indoc! {"
21022        one
21023        TWO
21024        THREE-HUNDRED
21025        FOUR
21026        five
21027    "}));
21028}
21029
21030#[gpui::test]
21031fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
21032    init_test(cx, |_| {});
21033
21034    let editor = cx.add_window(|window, cx| {
21035        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
21036        build_editor(buffer, window, cx)
21037    });
21038
21039    let render_args = Arc::new(Mutex::new(None));
21040    let snapshot = editor
21041        .update(cx, |editor, window, cx| {
21042            let snapshot = editor.buffer().read(cx).snapshot(cx);
21043            let range =
21044                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
21045
21046            struct RenderArgs {
21047                row: MultiBufferRow,
21048                folded: bool,
21049                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
21050            }
21051
21052            let crease = Crease::inline(
21053                range,
21054                FoldPlaceholder::test(),
21055                {
21056                    let toggle_callback = render_args.clone();
21057                    move |row, folded, callback, _window, _cx| {
21058                        *toggle_callback.lock() = Some(RenderArgs {
21059                            row,
21060                            folded,
21061                            callback,
21062                        });
21063                        div()
21064                    }
21065                },
21066                |_row, _folded, _window, _cx| div(),
21067            );
21068
21069            editor.insert_creases(Some(crease), cx);
21070            let snapshot = editor.snapshot(window, cx);
21071            let _div =
21072                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
21073            snapshot
21074        })
21075        .unwrap();
21076
21077    let render_args = render_args.lock().take().unwrap();
21078    assert_eq!(render_args.row, MultiBufferRow(1));
21079    assert!(!render_args.folded);
21080    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21081
21082    cx.update_window(*editor, |_, window, cx| {
21083        (render_args.callback)(true, window, cx)
21084    })
21085    .unwrap();
21086    let snapshot = editor
21087        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21088        .unwrap();
21089    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
21090
21091    cx.update_window(*editor, |_, window, cx| {
21092        (render_args.callback)(false, window, cx)
21093    })
21094    .unwrap();
21095    let snapshot = editor
21096        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21097        .unwrap();
21098    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21099}
21100
21101#[gpui::test]
21102async fn test_input_text(cx: &mut TestAppContext) {
21103    init_test(cx, |_| {});
21104    let mut cx = EditorTestContext::new(cx).await;
21105
21106    cx.set_state(
21107        &r#"ˇone
21108        two
21109
21110        three
21111        fourˇ
21112        five
21113
21114        siˇx"#
21115            .unindent(),
21116    );
21117
21118    cx.dispatch_action(HandleInput(String::new()));
21119    cx.assert_editor_state(
21120        &r#"ˇone
21121        two
21122
21123        three
21124        fourˇ
21125        five
21126
21127        siˇx"#
21128            .unindent(),
21129    );
21130
21131    cx.dispatch_action(HandleInput("AAAA".to_string()));
21132    cx.assert_editor_state(
21133        &r#"AAAAˇone
21134        two
21135
21136        three
21137        fourAAAAˇ
21138        five
21139
21140        siAAAAˇx"#
21141            .unindent(),
21142    );
21143}
21144
21145#[gpui::test]
21146async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
21147    init_test(cx, |_| {});
21148
21149    let mut cx = EditorTestContext::new(cx).await;
21150    cx.set_state(
21151        r#"let foo = 1;
21152let foo = 2;
21153let foo = 3;
21154let fooˇ = 4;
21155let foo = 5;
21156let foo = 6;
21157let foo = 7;
21158let foo = 8;
21159let foo = 9;
21160let foo = 10;
21161let foo = 11;
21162let foo = 12;
21163let foo = 13;
21164let foo = 14;
21165let foo = 15;"#,
21166    );
21167
21168    cx.update_editor(|e, window, cx| {
21169        assert_eq!(
21170            e.next_scroll_position,
21171            NextScrollCursorCenterTopBottom::Center,
21172            "Default next scroll direction is center",
21173        );
21174
21175        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21176        assert_eq!(
21177            e.next_scroll_position,
21178            NextScrollCursorCenterTopBottom::Top,
21179            "After center, next scroll direction should be top",
21180        );
21181
21182        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21183        assert_eq!(
21184            e.next_scroll_position,
21185            NextScrollCursorCenterTopBottom::Bottom,
21186            "After top, next scroll direction should be bottom",
21187        );
21188
21189        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21190        assert_eq!(
21191            e.next_scroll_position,
21192            NextScrollCursorCenterTopBottom::Center,
21193            "After bottom, scrolling should start over",
21194        );
21195
21196        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21197        assert_eq!(
21198            e.next_scroll_position,
21199            NextScrollCursorCenterTopBottom::Top,
21200            "Scrolling continues if retriggered fast enough"
21201        );
21202    });
21203
21204    cx.executor()
21205        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
21206    cx.executor().run_until_parked();
21207    cx.update_editor(|e, _, _| {
21208        assert_eq!(
21209            e.next_scroll_position,
21210            NextScrollCursorCenterTopBottom::Center,
21211            "If scrolling is not triggered fast enough, it should reset"
21212        );
21213    });
21214}
21215
21216#[gpui::test]
21217async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
21218    init_test(cx, |_| {});
21219    let mut cx = EditorLspTestContext::new_rust(
21220        lsp::ServerCapabilities {
21221            definition_provider: Some(lsp::OneOf::Left(true)),
21222            references_provider: Some(lsp::OneOf::Left(true)),
21223            ..lsp::ServerCapabilities::default()
21224        },
21225        cx,
21226    )
21227    .await;
21228
21229    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
21230        let go_to_definition = cx
21231            .lsp
21232            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21233                move |params, _| async move {
21234                    if empty_go_to_definition {
21235                        Ok(None)
21236                    } else {
21237                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
21238                            uri: params.text_document_position_params.text_document.uri,
21239                            range: lsp::Range::new(
21240                                lsp::Position::new(4, 3),
21241                                lsp::Position::new(4, 6),
21242                            ),
21243                        })))
21244                    }
21245                },
21246            );
21247        let references = cx
21248            .lsp
21249            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21250                Ok(Some(vec![lsp::Location {
21251                    uri: params.text_document_position.text_document.uri,
21252                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
21253                }]))
21254            });
21255        (go_to_definition, references)
21256    };
21257
21258    cx.set_state(
21259        &r#"fn one() {
21260            let mut a = ˇtwo();
21261        }
21262
21263        fn two() {}"#
21264            .unindent(),
21265    );
21266    set_up_lsp_handlers(false, &mut cx);
21267    let navigated = cx
21268        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21269        .await
21270        .expect("Failed to navigate to definition");
21271    assert_eq!(
21272        navigated,
21273        Navigated::Yes,
21274        "Should have navigated to definition from the GetDefinition response"
21275    );
21276    cx.assert_editor_state(
21277        &r#"fn one() {
21278            let mut a = two();
21279        }
21280
21281        fn «twoˇ»() {}"#
21282            .unindent(),
21283    );
21284
21285    let editors = cx.update_workspace(|workspace, _, cx| {
21286        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21287    });
21288    cx.update_editor(|_, _, test_editor_cx| {
21289        assert_eq!(
21290            editors.len(),
21291            1,
21292            "Initially, only one, test, editor should be open in the workspace"
21293        );
21294        assert_eq!(
21295            test_editor_cx.entity(),
21296            editors.last().expect("Asserted len is 1").clone()
21297        );
21298    });
21299
21300    set_up_lsp_handlers(true, &mut cx);
21301    let navigated = cx
21302        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21303        .await
21304        .expect("Failed to navigate to lookup references");
21305    assert_eq!(
21306        navigated,
21307        Navigated::Yes,
21308        "Should have navigated to references as a fallback after empty GoToDefinition response"
21309    );
21310    // We should not change the selections in the existing file,
21311    // if opening another milti buffer with the references
21312    cx.assert_editor_state(
21313        &r#"fn one() {
21314            let mut a = two();
21315        }
21316
21317        fn «twoˇ»() {}"#
21318            .unindent(),
21319    );
21320    let editors = cx.update_workspace(|workspace, _, cx| {
21321        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21322    });
21323    cx.update_editor(|_, _, test_editor_cx| {
21324        assert_eq!(
21325            editors.len(),
21326            2,
21327            "After falling back to references search, we open a new editor with the results"
21328        );
21329        let references_fallback_text = editors
21330            .into_iter()
21331            .find(|new_editor| *new_editor != test_editor_cx.entity())
21332            .expect("Should have one non-test editor now")
21333            .read(test_editor_cx)
21334            .text(test_editor_cx);
21335        assert_eq!(
21336            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
21337            "Should use the range from the references response and not the GoToDefinition one"
21338        );
21339    });
21340}
21341
21342#[gpui::test]
21343async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
21344    init_test(cx, |_| {});
21345    cx.update(|cx| {
21346        let mut editor_settings = EditorSettings::get_global(cx).clone();
21347        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
21348        EditorSettings::override_global(editor_settings, cx);
21349    });
21350    let mut cx = EditorLspTestContext::new_rust(
21351        lsp::ServerCapabilities {
21352            definition_provider: Some(lsp::OneOf::Left(true)),
21353            references_provider: Some(lsp::OneOf::Left(true)),
21354            ..lsp::ServerCapabilities::default()
21355        },
21356        cx,
21357    )
21358    .await;
21359    let original_state = r#"fn one() {
21360        let mut a = ˇtwo();
21361    }
21362
21363    fn two() {}"#
21364        .unindent();
21365    cx.set_state(&original_state);
21366
21367    let mut go_to_definition = cx
21368        .lsp
21369        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21370            move |_, _| async move { Ok(None) },
21371        );
21372    let _references = cx
21373        .lsp
21374        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
21375            panic!("Should not call for references with no go to definition fallback")
21376        });
21377
21378    let navigated = cx
21379        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21380        .await
21381        .expect("Failed to navigate to lookup references");
21382    go_to_definition
21383        .next()
21384        .await
21385        .expect("Should have called the go_to_definition handler");
21386
21387    assert_eq!(
21388        navigated,
21389        Navigated::No,
21390        "Should have navigated to references as a fallback after empty GoToDefinition response"
21391    );
21392    cx.assert_editor_state(&original_state);
21393    let editors = cx.update_workspace(|workspace, _, cx| {
21394        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21395    });
21396    cx.update_editor(|_, _, _| {
21397        assert_eq!(
21398            editors.len(),
21399            1,
21400            "After unsuccessful fallback, no other editor should have been opened"
21401        );
21402    });
21403}
21404
21405#[gpui::test]
21406async fn test_find_all_references_editor_reuse(cx: &mut TestAppContext) {
21407    init_test(cx, |_| {});
21408    let mut cx = EditorLspTestContext::new_rust(
21409        lsp::ServerCapabilities {
21410            references_provider: Some(lsp::OneOf::Left(true)),
21411            ..lsp::ServerCapabilities::default()
21412        },
21413        cx,
21414    )
21415    .await;
21416
21417    cx.set_state(
21418        &r#"
21419        fn one() {
21420            let mut a = two();
21421        }
21422
21423        fn ˇtwo() {}"#
21424            .unindent(),
21425    );
21426    cx.lsp
21427        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21428            Ok(Some(vec![
21429                lsp::Location {
21430                    uri: params.text_document_position.text_document.uri.clone(),
21431                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
21432                },
21433                lsp::Location {
21434                    uri: params.text_document_position.text_document.uri,
21435                    range: lsp::Range::new(lsp::Position::new(4, 4), lsp::Position::new(4, 7)),
21436                },
21437            ]))
21438        });
21439    let navigated = cx
21440        .update_editor(|editor, window, cx| {
21441            editor.find_all_references(&FindAllReferences, window, cx)
21442        })
21443        .unwrap()
21444        .await
21445        .expect("Failed to navigate to references");
21446    assert_eq!(
21447        navigated,
21448        Navigated::Yes,
21449        "Should have navigated to references from the FindAllReferences response"
21450    );
21451    cx.assert_editor_state(
21452        &r#"fn one() {
21453            let mut a = two();
21454        }
21455
21456        fn ˇtwo() {}"#
21457            .unindent(),
21458    );
21459
21460    let editors = cx.update_workspace(|workspace, _, cx| {
21461        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21462    });
21463    cx.update_editor(|_, _, _| {
21464        assert_eq!(editors.len(), 2, "We should have opened a new multibuffer");
21465    });
21466
21467    cx.set_state(
21468        &r#"fn one() {
21469            let mut a = ˇtwo();
21470        }
21471
21472        fn two() {}"#
21473            .unindent(),
21474    );
21475    let navigated = cx
21476        .update_editor(|editor, window, cx| {
21477            editor.find_all_references(&FindAllReferences, window, cx)
21478        })
21479        .unwrap()
21480        .await
21481        .expect("Failed to navigate to references");
21482    assert_eq!(
21483        navigated,
21484        Navigated::Yes,
21485        "Should have navigated to references from the FindAllReferences response"
21486    );
21487    cx.assert_editor_state(
21488        &r#"fn one() {
21489            let mut a = ˇtwo();
21490        }
21491
21492        fn two() {}"#
21493            .unindent(),
21494    );
21495    let editors = cx.update_workspace(|workspace, _, cx| {
21496        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21497    });
21498    cx.update_editor(|_, _, _| {
21499        assert_eq!(
21500            editors.len(),
21501            2,
21502            "should have re-used the previous multibuffer"
21503        );
21504    });
21505
21506    cx.set_state(
21507        &r#"fn one() {
21508            let mut a = ˇtwo();
21509        }
21510        fn three() {}
21511        fn two() {}"#
21512            .unindent(),
21513    );
21514    cx.lsp
21515        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21516            Ok(Some(vec![
21517                lsp::Location {
21518                    uri: params.text_document_position.text_document.uri.clone(),
21519                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
21520                },
21521                lsp::Location {
21522                    uri: params.text_document_position.text_document.uri,
21523                    range: lsp::Range::new(lsp::Position::new(5, 4), lsp::Position::new(5, 7)),
21524                },
21525            ]))
21526        });
21527    let navigated = cx
21528        .update_editor(|editor, window, cx| {
21529            editor.find_all_references(&FindAllReferences, window, cx)
21530        })
21531        .unwrap()
21532        .await
21533        .expect("Failed to navigate to references");
21534    assert_eq!(
21535        navigated,
21536        Navigated::Yes,
21537        "Should have navigated to references from the FindAllReferences response"
21538    );
21539    cx.assert_editor_state(
21540        &r#"fn one() {
21541                let mut a = ˇtwo();
21542            }
21543            fn three() {}
21544            fn two() {}"#
21545            .unindent(),
21546    );
21547    let editors = cx.update_workspace(|workspace, _, cx| {
21548        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21549    });
21550    cx.update_editor(|_, _, _| {
21551        assert_eq!(
21552            editors.len(),
21553            3,
21554            "should have used a new multibuffer as offsets changed"
21555        );
21556    });
21557}
21558#[gpui::test]
21559async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
21560    init_test(cx, |_| {});
21561
21562    let language = Arc::new(Language::new(
21563        LanguageConfig::default(),
21564        Some(tree_sitter_rust::LANGUAGE.into()),
21565    ));
21566
21567    let text = r#"
21568        #[cfg(test)]
21569        mod tests() {
21570            #[test]
21571            fn runnable_1() {
21572                let a = 1;
21573            }
21574
21575            #[test]
21576            fn runnable_2() {
21577                let a = 1;
21578                let b = 2;
21579            }
21580        }
21581    "#
21582    .unindent();
21583
21584    let fs = FakeFs::new(cx.executor());
21585    fs.insert_file("/file.rs", Default::default()).await;
21586
21587    let project = Project::test(fs, ["/a".as_ref()], cx).await;
21588    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21589    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21590    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
21591    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
21592
21593    let editor = cx.new_window_entity(|window, cx| {
21594        Editor::new(
21595            EditorMode::full(),
21596            multi_buffer,
21597            Some(project.clone()),
21598            window,
21599            cx,
21600        )
21601    });
21602
21603    editor.update_in(cx, |editor, window, cx| {
21604        let snapshot = editor.buffer().read(cx).snapshot(cx);
21605        editor.tasks.insert(
21606            (buffer.read(cx).remote_id(), 3),
21607            RunnableTasks {
21608                templates: vec![],
21609                offset: snapshot.anchor_before(43),
21610                column: 0,
21611                extra_variables: HashMap::default(),
21612                context_range: BufferOffset(43)..BufferOffset(85),
21613            },
21614        );
21615        editor.tasks.insert(
21616            (buffer.read(cx).remote_id(), 8),
21617            RunnableTasks {
21618                templates: vec![],
21619                offset: snapshot.anchor_before(86),
21620                column: 0,
21621                extra_variables: HashMap::default(),
21622                context_range: BufferOffset(86)..BufferOffset(191),
21623            },
21624        );
21625
21626        // Test finding task when cursor is inside function body
21627        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21628            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
21629        });
21630        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
21631        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
21632
21633        // Test finding task when cursor is on function name
21634        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21635            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
21636        });
21637        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
21638        assert_eq!(row, 8, "Should find task when cursor is on function name");
21639    });
21640}
21641
21642#[gpui::test]
21643async fn test_folding_buffers(cx: &mut TestAppContext) {
21644    init_test(cx, |_| {});
21645
21646    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
21647    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
21648    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
21649
21650    let fs = FakeFs::new(cx.executor());
21651    fs.insert_tree(
21652        path!("/a"),
21653        json!({
21654            "first.rs": sample_text_1,
21655            "second.rs": sample_text_2,
21656            "third.rs": sample_text_3,
21657        }),
21658    )
21659    .await;
21660    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21661    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21662    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21663    let worktree = project.update(cx, |project, cx| {
21664        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
21665        assert_eq!(worktrees.len(), 1);
21666        worktrees.pop().unwrap()
21667    });
21668    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
21669
21670    let buffer_1 = project
21671        .update(cx, |project, cx| {
21672            project.open_buffer((worktree_id, "first.rs"), cx)
21673        })
21674        .await
21675        .unwrap();
21676    let buffer_2 = project
21677        .update(cx, |project, cx| {
21678            project.open_buffer((worktree_id, "second.rs"), cx)
21679        })
21680        .await
21681        .unwrap();
21682    let buffer_3 = project
21683        .update(cx, |project, cx| {
21684            project.open_buffer((worktree_id, "third.rs"), cx)
21685        })
21686        .await
21687        .unwrap();
21688
21689    let multi_buffer = cx.new(|cx| {
21690        let mut multi_buffer = MultiBuffer::new(ReadWrite);
21691        multi_buffer.push_excerpts(
21692            buffer_1.clone(),
21693            [
21694                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21695                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21696                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21697            ],
21698            cx,
21699        );
21700        multi_buffer.push_excerpts(
21701            buffer_2.clone(),
21702            [
21703                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21704                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21705                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21706            ],
21707            cx,
21708        );
21709        multi_buffer.push_excerpts(
21710            buffer_3.clone(),
21711            [
21712                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21713                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21714                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21715            ],
21716            cx,
21717        );
21718        multi_buffer
21719    });
21720    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
21721        Editor::new(
21722            EditorMode::full(),
21723            multi_buffer.clone(),
21724            Some(project.clone()),
21725            window,
21726            cx,
21727        )
21728    });
21729
21730    assert_eq!(
21731        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21732        "\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",
21733    );
21734
21735    multi_buffer_editor.update(cx, |editor, cx| {
21736        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
21737    });
21738    assert_eq!(
21739        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21740        "\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",
21741        "After folding the first buffer, its text should not be displayed"
21742    );
21743
21744    multi_buffer_editor.update(cx, |editor, cx| {
21745        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
21746    });
21747    assert_eq!(
21748        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21749        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
21750        "After folding the second buffer, its text should not be displayed"
21751    );
21752
21753    multi_buffer_editor.update(cx, |editor, cx| {
21754        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
21755    });
21756    assert_eq!(
21757        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21758        "\n\n\n\n\n",
21759        "After folding the third buffer, its text should not be displayed"
21760    );
21761
21762    // Emulate selection inside the fold logic, that should work
21763    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21764        editor
21765            .snapshot(window, cx)
21766            .next_line_boundary(Point::new(0, 4));
21767    });
21768
21769    multi_buffer_editor.update(cx, |editor, cx| {
21770        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
21771    });
21772    assert_eq!(
21773        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21774        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
21775        "After unfolding the second buffer, its text should be displayed"
21776    );
21777
21778    // Typing inside of buffer 1 causes that buffer to be unfolded.
21779    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21780        assert_eq!(
21781            multi_buffer
21782                .read(cx)
21783                .snapshot(cx)
21784                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
21785                .collect::<String>(),
21786            "bbbb"
21787        );
21788        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21789            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
21790        });
21791        editor.handle_input("B", window, cx);
21792    });
21793
21794    assert_eq!(
21795        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21796        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
21797        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
21798    );
21799
21800    multi_buffer_editor.update(cx, |editor, cx| {
21801        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
21802    });
21803    assert_eq!(
21804        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21805        "\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",
21806        "After unfolding the all buffers, all original text should be displayed"
21807    );
21808}
21809
21810#[gpui::test]
21811async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
21812    init_test(cx, |_| {});
21813
21814    let sample_text_1 = "1111\n2222\n3333".to_string();
21815    let sample_text_2 = "4444\n5555\n6666".to_string();
21816    let sample_text_3 = "7777\n8888\n9999".to_string();
21817
21818    let fs = FakeFs::new(cx.executor());
21819    fs.insert_tree(
21820        path!("/a"),
21821        json!({
21822            "first.rs": sample_text_1,
21823            "second.rs": sample_text_2,
21824            "third.rs": sample_text_3,
21825        }),
21826    )
21827    .await;
21828    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21829    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21830    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21831    let worktree = project.update(cx, |project, cx| {
21832        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
21833        assert_eq!(worktrees.len(), 1);
21834        worktrees.pop().unwrap()
21835    });
21836    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
21837
21838    let buffer_1 = project
21839        .update(cx, |project, cx| {
21840            project.open_buffer((worktree_id, "first.rs"), cx)
21841        })
21842        .await
21843        .unwrap();
21844    let buffer_2 = project
21845        .update(cx, |project, cx| {
21846            project.open_buffer((worktree_id, "second.rs"), cx)
21847        })
21848        .await
21849        .unwrap();
21850    let buffer_3 = project
21851        .update(cx, |project, cx| {
21852            project.open_buffer((worktree_id, "third.rs"), cx)
21853        })
21854        .await
21855        .unwrap();
21856
21857    let multi_buffer = cx.new(|cx| {
21858        let mut multi_buffer = MultiBuffer::new(ReadWrite);
21859        multi_buffer.push_excerpts(
21860            buffer_1.clone(),
21861            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21862            cx,
21863        );
21864        multi_buffer.push_excerpts(
21865            buffer_2.clone(),
21866            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21867            cx,
21868        );
21869        multi_buffer.push_excerpts(
21870            buffer_3.clone(),
21871            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21872            cx,
21873        );
21874        multi_buffer
21875    });
21876
21877    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
21878        Editor::new(
21879            EditorMode::full(),
21880            multi_buffer,
21881            Some(project.clone()),
21882            window,
21883            cx,
21884        )
21885    });
21886
21887    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
21888    assert_eq!(
21889        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21890        full_text,
21891    );
21892
21893    multi_buffer_editor.update(cx, |editor, cx| {
21894        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
21895    });
21896    assert_eq!(
21897        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21898        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
21899        "After folding the first buffer, its text should not be displayed"
21900    );
21901
21902    multi_buffer_editor.update(cx, |editor, cx| {
21903        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
21904    });
21905
21906    assert_eq!(
21907        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21908        "\n\n\n\n\n\n7777\n8888\n9999",
21909        "After folding the second buffer, its text should not be displayed"
21910    );
21911
21912    multi_buffer_editor.update(cx, |editor, cx| {
21913        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
21914    });
21915    assert_eq!(
21916        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21917        "\n\n\n\n\n",
21918        "After folding the third buffer, its text should not be displayed"
21919    );
21920
21921    multi_buffer_editor.update(cx, |editor, cx| {
21922        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
21923    });
21924    assert_eq!(
21925        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21926        "\n\n\n\n4444\n5555\n6666\n\n",
21927        "After unfolding the second buffer, its text should be displayed"
21928    );
21929
21930    multi_buffer_editor.update(cx, |editor, cx| {
21931        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
21932    });
21933    assert_eq!(
21934        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21935        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
21936        "After unfolding the first buffer, its text should be displayed"
21937    );
21938
21939    multi_buffer_editor.update(cx, |editor, cx| {
21940        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
21941    });
21942    assert_eq!(
21943        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21944        full_text,
21945        "After unfolding all buffers, all original text should be displayed"
21946    );
21947}
21948
21949#[gpui::test]
21950async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
21951    init_test(cx, |_| {});
21952
21953    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
21954
21955    let fs = FakeFs::new(cx.executor());
21956    fs.insert_tree(
21957        path!("/a"),
21958        json!({
21959            "main.rs": sample_text,
21960        }),
21961    )
21962    .await;
21963    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21964    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21965    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21966    let worktree = project.update(cx, |project, cx| {
21967        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
21968        assert_eq!(worktrees.len(), 1);
21969        worktrees.pop().unwrap()
21970    });
21971    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
21972
21973    let buffer_1 = project
21974        .update(cx, |project, cx| {
21975            project.open_buffer((worktree_id, "main.rs"), cx)
21976        })
21977        .await
21978        .unwrap();
21979
21980    let multi_buffer = cx.new(|cx| {
21981        let mut multi_buffer = MultiBuffer::new(ReadWrite);
21982        multi_buffer.push_excerpts(
21983            buffer_1.clone(),
21984            [ExcerptRange::new(
21985                Point::new(0, 0)
21986                    ..Point::new(
21987                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
21988                        0,
21989                    ),
21990            )],
21991            cx,
21992        );
21993        multi_buffer
21994    });
21995    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
21996        Editor::new(
21997            EditorMode::full(),
21998            multi_buffer,
21999            Some(project.clone()),
22000            window,
22001            cx,
22002        )
22003    });
22004
22005    let selection_range = Point::new(1, 0)..Point::new(2, 0);
22006    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22007        enum TestHighlight {}
22008        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
22009        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
22010        editor.highlight_text::<TestHighlight>(
22011            vec![highlight_range.clone()],
22012            HighlightStyle::color(Hsla::green()),
22013            cx,
22014        );
22015        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22016            s.select_ranges(Some(highlight_range))
22017        });
22018    });
22019
22020    let full_text = format!("\n\n{sample_text}");
22021    assert_eq!(
22022        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22023        full_text,
22024    );
22025}
22026
22027#[gpui::test]
22028async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
22029    init_test(cx, |_| {});
22030    cx.update(|cx| {
22031        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
22032            "keymaps/default-linux.json",
22033            cx,
22034        )
22035        .unwrap();
22036        cx.bind_keys(default_key_bindings);
22037    });
22038
22039    let (editor, cx) = cx.add_window_view(|window, cx| {
22040        let multi_buffer = MultiBuffer::build_multi(
22041            [
22042                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
22043                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
22044                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
22045                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
22046            ],
22047            cx,
22048        );
22049        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
22050
22051        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
22052        // fold all but the second buffer, so that we test navigating between two
22053        // adjacent folded buffers, as well as folded buffers at the start and
22054        // end the multibuffer
22055        editor.fold_buffer(buffer_ids[0], cx);
22056        editor.fold_buffer(buffer_ids[2], cx);
22057        editor.fold_buffer(buffer_ids[3], cx);
22058
22059        editor
22060    });
22061    cx.simulate_resize(size(px(1000.), px(1000.)));
22062
22063    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
22064    cx.assert_excerpts_with_selections(indoc! {"
22065        [EXCERPT]
22066        ˇ[FOLDED]
22067        [EXCERPT]
22068        a1
22069        b1
22070        [EXCERPT]
22071        [FOLDED]
22072        [EXCERPT]
22073        [FOLDED]
22074        "
22075    });
22076    cx.simulate_keystroke("down");
22077    cx.assert_excerpts_with_selections(indoc! {"
22078        [EXCERPT]
22079        [FOLDED]
22080        [EXCERPT]
22081        ˇa1
22082        b1
22083        [EXCERPT]
22084        [FOLDED]
22085        [EXCERPT]
22086        [FOLDED]
22087        "
22088    });
22089    cx.simulate_keystroke("down");
22090    cx.assert_excerpts_with_selections(indoc! {"
22091        [EXCERPT]
22092        [FOLDED]
22093        [EXCERPT]
22094        a1
22095        ˇb1
22096        [EXCERPT]
22097        [FOLDED]
22098        [EXCERPT]
22099        [FOLDED]
22100        "
22101    });
22102    cx.simulate_keystroke("down");
22103    cx.assert_excerpts_with_selections(indoc! {"
22104        [EXCERPT]
22105        [FOLDED]
22106        [EXCERPT]
22107        a1
22108        b1
22109        ˇ[EXCERPT]
22110        [FOLDED]
22111        [EXCERPT]
22112        [FOLDED]
22113        "
22114    });
22115    cx.simulate_keystroke("down");
22116    cx.assert_excerpts_with_selections(indoc! {"
22117        [EXCERPT]
22118        [FOLDED]
22119        [EXCERPT]
22120        a1
22121        b1
22122        [EXCERPT]
22123        ˇ[FOLDED]
22124        [EXCERPT]
22125        [FOLDED]
22126        "
22127    });
22128    for _ in 0..5 {
22129        cx.simulate_keystroke("down");
22130        cx.assert_excerpts_with_selections(indoc! {"
22131            [EXCERPT]
22132            [FOLDED]
22133            [EXCERPT]
22134            a1
22135            b1
22136            [EXCERPT]
22137            [FOLDED]
22138            [EXCERPT]
22139            ˇ[FOLDED]
22140            "
22141        });
22142    }
22143
22144    cx.simulate_keystroke("up");
22145    cx.assert_excerpts_with_selections(indoc! {"
22146        [EXCERPT]
22147        [FOLDED]
22148        [EXCERPT]
22149        a1
22150        b1
22151        [EXCERPT]
22152        ˇ[FOLDED]
22153        [EXCERPT]
22154        [FOLDED]
22155        "
22156    });
22157    cx.simulate_keystroke("up");
22158    cx.assert_excerpts_with_selections(indoc! {"
22159        [EXCERPT]
22160        [FOLDED]
22161        [EXCERPT]
22162        a1
22163        b1
22164        ˇ[EXCERPT]
22165        [FOLDED]
22166        [EXCERPT]
22167        [FOLDED]
22168        "
22169    });
22170    cx.simulate_keystroke("up");
22171    cx.assert_excerpts_with_selections(indoc! {"
22172        [EXCERPT]
22173        [FOLDED]
22174        [EXCERPT]
22175        a1
22176        ˇb1
22177        [EXCERPT]
22178        [FOLDED]
22179        [EXCERPT]
22180        [FOLDED]
22181        "
22182    });
22183    cx.simulate_keystroke("up");
22184    cx.assert_excerpts_with_selections(indoc! {"
22185        [EXCERPT]
22186        [FOLDED]
22187        [EXCERPT]
22188        ˇa1
22189        b1
22190        [EXCERPT]
22191        [FOLDED]
22192        [EXCERPT]
22193        [FOLDED]
22194        "
22195    });
22196    for _ in 0..5 {
22197        cx.simulate_keystroke("up");
22198        cx.assert_excerpts_with_selections(indoc! {"
22199            [EXCERPT]
22200            ˇ[FOLDED]
22201            [EXCERPT]
22202            a1
22203            b1
22204            [EXCERPT]
22205            [FOLDED]
22206            [EXCERPT]
22207            [FOLDED]
22208            "
22209        });
22210    }
22211}
22212
22213#[gpui::test]
22214async fn test_edit_prediction_text(cx: &mut TestAppContext) {
22215    init_test(cx, |_| {});
22216
22217    // Simple insertion
22218    assert_highlighted_edits(
22219        "Hello, world!",
22220        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
22221        true,
22222        cx,
22223        |highlighted_edits, cx| {
22224            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
22225            assert_eq!(highlighted_edits.highlights.len(), 1);
22226            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
22227            assert_eq!(
22228                highlighted_edits.highlights[0].1.background_color,
22229                Some(cx.theme().status().created_background)
22230            );
22231        },
22232    )
22233    .await;
22234
22235    // Replacement
22236    assert_highlighted_edits(
22237        "This is a test.",
22238        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
22239        false,
22240        cx,
22241        |highlighted_edits, cx| {
22242            assert_eq!(highlighted_edits.text, "That is a test.");
22243            assert_eq!(highlighted_edits.highlights.len(), 1);
22244            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
22245            assert_eq!(
22246                highlighted_edits.highlights[0].1.background_color,
22247                Some(cx.theme().status().created_background)
22248            );
22249        },
22250    )
22251    .await;
22252
22253    // Multiple edits
22254    assert_highlighted_edits(
22255        "Hello, world!",
22256        vec![
22257            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
22258            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
22259        ],
22260        false,
22261        cx,
22262        |highlighted_edits, cx| {
22263            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
22264            assert_eq!(highlighted_edits.highlights.len(), 2);
22265            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
22266            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
22267            assert_eq!(
22268                highlighted_edits.highlights[0].1.background_color,
22269                Some(cx.theme().status().created_background)
22270            );
22271            assert_eq!(
22272                highlighted_edits.highlights[1].1.background_color,
22273                Some(cx.theme().status().created_background)
22274            );
22275        },
22276    )
22277    .await;
22278
22279    // Multiple lines with edits
22280    assert_highlighted_edits(
22281        "First line\nSecond line\nThird line\nFourth line",
22282        vec![
22283            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
22284            (
22285                Point::new(2, 0)..Point::new(2, 10),
22286                "New third line".to_string(),
22287            ),
22288            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
22289        ],
22290        false,
22291        cx,
22292        |highlighted_edits, cx| {
22293            assert_eq!(
22294                highlighted_edits.text,
22295                "Second modified\nNew third line\nFourth updated line"
22296            );
22297            assert_eq!(highlighted_edits.highlights.len(), 3);
22298            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
22299            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
22300            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
22301            for highlight in &highlighted_edits.highlights {
22302                assert_eq!(
22303                    highlight.1.background_color,
22304                    Some(cx.theme().status().created_background)
22305                );
22306            }
22307        },
22308    )
22309    .await;
22310}
22311
22312#[gpui::test]
22313async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
22314    init_test(cx, |_| {});
22315
22316    // Deletion
22317    assert_highlighted_edits(
22318        "Hello, world!",
22319        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
22320        true,
22321        cx,
22322        |highlighted_edits, cx| {
22323            assert_eq!(highlighted_edits.text, "Hello, world!");
22324            assert_eq!(highlighted_edits.highlights.len(), 1);
22325            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
22326            assert_eq!(
22327                highlighted_edits.highlights[0].1.background_color,
22328                Some(cx.theme().status().deleted_background)
22329            );
22330        },
22331    )
22332    .await;
22333
22334    // Insertion
22335    assert_highlighted_edits(
22336        "Hello, world!",
22337        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
22338        true,
22339        cx,
22340        |highlighted_edits, cx| {
22341            assert_eq!(highlighted_edits.highlights.len(), 1);
22342            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
22343            assert_eq!(
22344                highlighted_edits.highlights[0].1.background_color,
22345                Some(cx.theme().status().created_background)
22346            );
22347        },
22348    )
22349    .await;
22350}
22351
22352async fn assert_highlighted_edits(
22353    text: &str,
22354    edits: Vec<(Range<Point>, String)>,
22355    include_deletions: bool,
22356    cx: &mut TestAppContext,
22357    assertion_fn: impl Fn(HighlightedText, &App),
22358) {
22359    let window = cx.add_window(|window, cx| {
22360        let buffer = MultiBuffer::build_simple(text, cx);
22361        Editor::new(EditorMode::full(), buffer, None, window, cx)
22362    });
22363    let cx = &mut VisualTestContext::from_window(*window, cx);
22364
22365    let (buffer, snapshot) = window
22366        .update(cx, |editor, _window, cx| {
22367            (
22368                editor.buffer().clone(),
22369                editor.buffer().read(cx).snapshot(cx),
22370            )
22371        })
22372        .unwrap();
22373
22374    let edits = edits
22375        .into_iter()
22376        .map(|(range, edit)| {
22377            (
22378                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
22379                edit,
22380            )
22381        })
22382        .collect::<Vec<_>>();
22383
22384    let text_anchor_edits = edits
22385        .clone()
22386        .into_iter()
22387        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
22388        .collect::<Vec<_>>();
22389
22390    let edit_preview = window
22391        .update(cx, |_, _window, cx| {
22392            buffer
22393                .read(cx)
22394                .as_singleton()
22395                .unwrap()
22396                .read(cx)
22397                .preview_edits(text_anchor_edits.into(), cx)
22398        })
22399        .unwrap()
22400        .await;
22401
22402    cx.update(|_window, cx| {
22403        let highlighted_edits = edit_prediction_edit_text(
22404            snapshot.as_singleton().unwrap().2,
22405            &edits,
22406            &edit_preview,
22407            include_deletions,
22408            cx,
22409        );
22410        assertion_fn(highlighted_edits, cx)
22411    });
22412}
22413
22414#[track_caller]
22415fn assert_breakpoint(
22416    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
22417    path: &Arc<Path>,
22418    expected: Vec<(u32, Breakpoint)>,
22419) {
22420    if expected.is_empty() {
22421        assert!(!breakpoints.contains_key(path), "{}", path.display());
22422    } else {
22423        let mut breakpoint = breakpoints
22424            .get(path)
22425            .unwrap()
22426            .iter()
22427            .map(|breakpoint| {
22428                (
22429                    breakpoint.row,
22430                    Breakpoint {
22431                        message: breakpoint.message.clone(),
22432                        state: breakpoint.state,
22433                        condition: breakpoint.condition.clone(),
22434                        hit_condition: breakpoint.hit_condition.clone(),
22435                    },
22436                )
22437            })
22438            .collect::<Vec<_>>();
22439
22440        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
22441
22442        assert_eq!(expected, breakpoint);
22443    }
22444}
22445
22446fn add_log_breakpoint_at_cursor(
22447    editor: &mut Editor,
22448    log_message: &str,
22449    window: &mut Window,
22450    cx: &mut Context<Editor>,
22451) {
22452    let (anchor, bp) = editor
22453        .breakpoints_at_cursors(window, cx)
22454        .first()
22455        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
22456        .unwrap_or_else(|| {
22457            let cursor_position: Point = editor.selections.newest(cx).head();
22458
22459            let breakpoint_position = editor
22460                .snapshot(window, cx)
22461                .display_snapshot
22462                .buffer_snapshot
22463                .anchor_before(Point::new(cursor_position.row, 0));
22464
22465            (breakpoint_position, Breakpoint::new_log(log_message))
22466        });
22467
22468    editor.edit_breakpoint_at_anchor(
22469        anchor,
22470        bp,
22471        BreakpointEditAction::EditLogMessage(log_message.into()),
22472        cx,
22473    );
22474}
22475
22476#[gpui::test]
22477async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
22478    init_test(cx, |_| {});
22479
22480    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22481    let fs = FakeFs::new(cx.executor());
22482    fs.insert_tree(
22483        path!("/a"),
22484        json!({
22485            "main.rs": sample_text,
22486        }),
22487    )
22488    .await;
22489    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22490    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22491    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22492
22493    let fs = FakeFs::new(cx.executor());
22494    fs.insert_tree(
22495        path!("/a"),
22496        json!({
22497            "main.rs": sample_text,
22498        }),
22499    )
22500    .await;
22501    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22502    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22503    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22504    let worktree_id = workspace
22505        .update(cx, |workspace, _window, cx| {
22506            workspace.project().update(cx, |project, cx| {
22507                project.worktrees(cx).next().unwrap().read(cx).id()
22508            })
22509        })
22510        .unwrap();
22511
22512    let buffer = project
22513        .update(cx, |project, cx| {
22514            project.open_buffer((worktree_id, "main.rs"), cx)
22515        })
22516        .await
22517        .unwrap();
22518
22519    let (editor, cx) = cx.add_window_view(|window, cx| {
22520        Editor::new(
22521            EditorMode::full(),
22522            MultiBuffer::build_from_buffer(buffer, cx),
22523            Some(project.clone()),
22524            window,
22525            cx,
22526        )
22527    });
22528
22529    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22530    let abs_path = project.read_with(cx, |project, cx| {
22531        project
22532            .absolute_path(&project_path, cx)
22533            .map(Arc::from)
22534            .unwrap()
22535    });
22536
22537    // assert we can add breakpoint on the first line
22538    editor.update_in(cx, |editor, window, cx| {
22539        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22540        editor.move_to_end(&MoveToEnd, window, cx);
22541        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22542    });
22543
22544    let breakpoints = editor.update(cx, |editor, cx| {
22545        editor
22546            .breakpoint_store()
22547            .as_ref()
22548            .unwrap()
22549            .read(cx)
22550            .all_source_breakpoints(cx)
22551    });
22552
22553    assert_eq!(1, breakpoints.len());
22554    assert_breakpoint(
22555        &breakpoints,
22556        &abs_path,
22557        vec![
22558            (0, Breakpoint::new_standard()),
22559            (3, Breakpoint::new_standard()),
22560        ],
22561    );
22562
22563    editor.update_in(cx, |editor, window, cx| {
22564        editor.move_to_beginning(&MoveToBeginning, window, cx);
22565        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22566    });
22567
22568    let breakpoints = editor.update(cx, |editor, cx| {
22569        editor
22570            .breakpoint_store()
22571            .as_ref()
22572            .unwrap()
22573            .read(cx)
22574            .all_source_breakpoints(cx)
22575    });
22576
22577    assert_eq!(1, breakpoints.len());
22578    assert_breakpoint(
22579        &breakpoints,
22580        &abs_path,
22581        vec![(3, Breakpoint::new_standard())],
22582    );
22583
22584    editor.update_in(cx, |editor, window, cx| {
22585        editor.move_to_end(&MoveToEnd, window, cx);
22586        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22587    });
22588
22589    let breakpoints = editor.update(cx, |editor, cx| {
22590        editor
22591            .breakpoint_store()
22592            .as_ref()
22593            .unwrap()
22594            .read(cx)
22595            .all_source_breakpoints(cx)
22596    });
22597
22598    assert_eq!(0, breakpoints.len());
22599    assert_breakpoint(&breakpoints, &abs_path, vec![]);
22600}
22601
22602#[gpui::test]
22603async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
22604    init_test(cx, |_| {});
22605
22606    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22607
22608    let fs = FakeFs::new(cx.executor());
22609    fs.insert_tree(
22610        path!("/a"),
22611        json!({
22612            "main.rs": sample_text,
22613        }),
22614    )
22615    .await;
22616    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22617    let (workspace, cx) =
22618        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22619
22620    let worktree_id = workspace.update(cx, |workspace, cx| {
22621        workspace.project().update(cx, |project, cx| {
22622            project.worktrees(cx).next().unwrap().read(cx).id()
22623        })
22624    });
22625
22626    let buffer = project
22627        .update(cx, |project, cx| {
22628            project.open_buffer((worktree_id, "main.rs"), cx)
22629        })
22630        .await
22631        .unwrap();
22632
22633    let (editor, cx) = cx.add_window_view(|window, cx| {
22634        Editor::new(
22635            EditorMode::full(),
22636            MultiBuffer::build_from_buffer(buffer, cx),
22637            Some(project.clone()),
22638            window,
22639            cx,
22640        )
22641    });
22642
22643    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22644    let abs_path = project.read_with(cx, |project, cx| {
22645        project
22646            .absolute_path(&project_path, cx)
22647            .map(Arc::from)
22648            .unwrap()
22649    });
22650
22651    editor.update_in(cx, |editor, window, cx| {
22652        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
22653    });
22654
22655    let breakpoints = editor.update(cx, |editor, cx| {
22656        editor
22657            .breakpoint_store()
22658            .as_ref()
22659            .unwrap()
22660            .read(cx)
22661            .all_source_breakpoints(cx)
22662    });
22663
22664    assert_breakpoint(
22665        &breakpoints,
22666        &abs_path,
22667        vec![(0, Breakpoint::new_log("hello world"))],
22668    );
22669
22670    // Removing a log message from a log breakpoint should remove it
22671    editor.update_in(cx, |editor, window, cx| {
22672        add_log_breakpoint_at_cursor(editor, "", window, cx);
22673    });
22674
22675    let breakpoints = editor.update(cx, |editor, cx| {
22676        editor
22677            .breakpoint_store()
22678            .as_ref()
22679            .unwrap()
22680            .read(cx)
22681            .all_source_breakpoints(cx)
22682    });
22683
22684    assert_breakpoint(&breakpoints, &abs_path, vec![]);
22685
22686    editor.update_in(cx, |editor, window, cx| {
22687        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22688        editor.move_to_end(&MoveToEnd, window, cx);
22689        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22690        // Not adding a log message to a standard breakpoint shouldn't remove it
22691        add_log_breakpoint_at_cursor(editor, "", window, cx);
22692    });
22693
22694    let breakpoints = editor.update(cx, |editor, cx| {
22695        editor
22696            .breakpoint_store()
22697            .as_ref()
22698            .unwrap()
22699            .read(cx)
22700            .all_source_breakpoints(cx)
22701    });
22702
22703    assert_breakpoint(
22704        &breakpoints,
22705        &abs_path,
22706        vec![
22707            (0, Breakpoint::new_standard()),
22708            (3, Breakpoint::new_standard()),
22709        ],
22710    );
22711
22712    editor.update_in(cx, |editor, window, cx| {
22713        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
22714    });
22715
22716    let breakpoints = editor.update(cx, |editor, cx| {
22717        editor
22718            .breakpoint_store()
22719            .as_ref()
22720            .unwrap()
22721            .read(cx)
22722            .all_source_breakpoints(cx)
22723    });
22724
22725    assert_breakpoint(
22726        &breakpoints,
22727        &abs_path,
22728        vec![
22729            (0, Breakpoint::new_standard()),
22730            (3, Breakpoint::new_log("hello world")),
22731        ],
22732    );
22733
22734    editor.update_in(cx, |editor, window, cx| {
22735        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
22736    });
22737
22738    let breakpoints = editor.update(cx, |editor, cx| {
22739        editor
22740            .breakpoint_store()
22741            .as_ref()
22742            .unwrap()
22743            .read(cx)
22744            .all_source_breakpoints(cx)
22745    });
22746
22747    assert_breakpoint(
22748        &breakpoints,
22749        &abs_path,
22750        vec![
22751            (0, Breakpoint::new_standard()),
22752            (3, Breakpoint::new_log("hello Earth!!")),
22753        ],
22754    );
22755}
22756
22757/// This also tests that Editor::breakpoint_at_cursor_head is working properly
22758/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
22759/// or when breakpoints were placed out of order. This tests for a regression too
22760#[gpui::test]
22761async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
22762    init_test(cx, |_| {});
22763
22764    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22765    let fs = FakeFs::new(cx.executor());
22766    fs.insert_tree(
22767        path!("/a"),
22768        json!({
22769            "main.rs": sample_text,
22770        }),
22771    )
22772    .await;
22773    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22774    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22775    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22776
22777    let fs = FakeFs::new(cx.executor());
22778    fs.insert_tree(
22779        path!("/a"),
22780        json!({
22781            "main.rs": sample_text,
22782        }),
22783    )
22784    .await;
22785    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22786    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22787    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22788    let worktree_id = workspace
22789        .update(cx, |workspace, _window, cx| {
22790            workspace.project().update(cx, |project, cx| {
22791                project.worktrees(cx).next().unwrap().read(cx).id()
22792            })
22793        })
22794        .unwrap();
22795
22796    let buffer = project
22797        .update(cx, |project, cx| {
22798            project.open_buffer((worktree_id, "main.rs"), cx)
22799        })
22800        .await
22801        .unwrap();
22802
22803    let (editor, cx) = cx.add_window_view(|window, cx| {
22804        Editor::new(
22805            EditorMode::full(),
22806            MultiBuffer::build_from_buffer(buffer, cx),
22807            Some(project.clone()),
22808            window,
22809            cx,
22810        )
22811    });
22812
22813    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22814    let abs_path = project.read_with(cx, |project, cx| {
22815        project
22816            .absolute_path(&project_path, cx)
22817            .map(Arc::from)
22818            .unwrap()
22819    });
22820
22821    // assert we can add breakpoint on the first line
22822    editor.update_in(cx, |editor, window, cx| {
22823        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22824        editor.move_to_end(&MoveToEnd, window, cx);
22825        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22826        editor.move_up(&MoveUp, window, cx);
22827        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22828    });
22829
22830    let breakpoints = editor.update(cx, |editor, cx| {
22831        editor
22832            .breakpoint_store()
22833            .as_ref()
22834            .unwrap()
22835            .read(cx)
22836            .all_source_breakpoints(cx)
22837    });
22838
22839    assert_eq!(1, breakpoints.len());
22840    assert_breakpoint(
22841        &breakpoints,
22842        &abs_path,
22843        vec![
22844            (0, Breakpoint::new_standard()),
22845            (2, Breakpoint::new_standard()),
22846            (3, Breakpoint::new_standard()),
22847        ],
22848    );
22849
22850    editor.update_in(cx, |editor, window, cx| {
22851        editor.move_to_beginning(&MoveToBeginning, window, cx);
22852        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22853        editor.move_to_end(&MoveToEnd, window, cx);
22854        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22855        // Disabling a breakpoint that doesn't exist should do nothing
22856        editor.move_up(&MoveUp, window, cx);
22857        editor.move_up(&MoveUp, window, cx);
22858        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22859    });
22860
22861    let breakpoints = editor.update(cx, |editor, cx| {
22862        editor
22863            .breakpoint_store()
22864            .as_ref()
22865            .unwrap()
22866            .read(cx)
22867            .all_source_breakpoints(cx)
22868    });
22869
22870    let disable_breakpoint = {
22871        let mut bp = Breakpoint::new_standard();
22872        bp.state = BreakpointState::Disabled;
22873        bp
22874    };
22875
22876    assert_eq!(1, breakpoints.len());
22877    assert_breakpoint(
22878        &breakpoints,
22879        &abs_path,
22880        vec![
22881            (0, disable_breakpoint.clone()),
22882            (2, Breakpoint::new_standard()),
22883            (3, disable_breakpoint.clone()),
22884        ],
22885    );
22886
22887    editor.update_in(cx, |editor, window, cx| {
22888        editor.move_to_beginning(&MoveToBeginning, window, cx);
22889        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
22890        editor.move_to_end(&MoveToEnd, window, cx);
22891        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
22892        editor.move_up(&MoveUp, window, cx);
22893        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22894    });
22895
22896    let breakpoints = editor.update(cx, |editor, cx| {
22897        editor
22898            .breakpoint_store()
22899            .as_ref()
22900            .unwrap()
22901            .read(cx)
22902            .all_source_breakpoints(cx)
22903    });
22904
22905    assert_eq!(1, breakpoints.len());
22906    assert_breakpoint(
22907        &breakpoints,
22908        &abs_path,
22909        vec![
22910            (0, Breakpoint::new_standard()),
22911            (2, disable_breakpoint),
22912            (3, Breakpoint::new_standard()),
22913        ],
22914    );
22915}
22916
22917#[gpui::test]
22918async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
22919    init_test(cx, |_| {});
22920    let capabilities = lsp::ServerCapabilities {
22921        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
22922            prepare_provider: Some(true),
22923            work_done_progress_options: Default::default(),
22924        })),
22925        ..Default::default()
22926    };
22927    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
22928
22929    cx.set_state(indoc! {"
22930        struct Fˇoo {}
22931    "});
22932
22933    cx.update_editor(|editor, _, cx| {
22934        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
22935        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
22936        editor.highlight_background::<DocumentHighlightRead>(
22937            &[highlight_range],
22938            |theme| theme.colors().editor_document_highlight_read_background,
22939            cx,
22940        );
22941    });
22942
22943    let mut prepare_rename_handler = cx
22944        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
22945            move |_, _, _| async move {
22946                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
22947                    start: lsp::Position {
22948                        line: 0,
22949                        character: 7,
22950                    },
22951                    end: lsp::Position {
22952                        line: 0,
22953                        character: 10,
22954                    },
22955                })))
22956            },
22957        );
22958    let prepare_rename_task = cx
22959        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
22960        .expect("Prepare rename was not started");
22961    prepare_rename_handler.next().await.unwrap();
22962    prepare_rename_task.await.expect("Prepare rename failed");
22963
22964    let mut rename_handler =
22965        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
22966            let edit = lsp::TextEdit {
22967                range: lsp::Range {
22968                    start: lsp::Position {
22969                        line: 0,
22970                        character: 7,
22971                    },
22972                    end: lsp::Position {
22973                        line: 0,
22974                        character: 10,
22975                    },
22976                },
22977                new_text: "FooRenamed".to_string(),
22978            };
22979            Ok(Some(lsp::WorkspaceEdit::new(
22980                // Specify the same edit twice
22981                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
22982            )))
22983        });
22984    let rename_task = cx
22985        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
22986        .expect("Confirm rename was not started");
22987    rename_handler.next().await.unwrap();
22988    rename_task.await.expect("Confirm rename failed");
22989    cx.run_until_parked();
22990
22991    // Despite two edits, only one is actually applied as those are identical
22992    cx.assert_editor_state(indoc! {"
22993        struct FooRenamedˇ {}
22994    "});
22995}
22996
22997#[gpui::test]
22998async fn test_rename_without_prepare(cx: &mut TestAppContext) {
22999    init_test(cx, |_| {});
23000    // These capabilities indicate that the server does not support prepare rename.
23001    let capabilities = lsp::ServerCapabilities {
23002        rename_provider: Some(lsp::OneOf::Left(true)),
23003        ..Default::default()
23004    };
23005    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
23006
23007    cx.set_state(indoc! {"
23008        struct Fˇoo {}
23009    "});
23010
23011    cx.update_editor(|editor, _window, cx| {
23012        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
23013        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
23014        editor.highlight_background::<DocumentHighlightRead>(
23015            &[highlight_range],
23016            |theme| theme.colors().editor_document_highlight_read_background,
23017            cx,
23018        );
23019    });
23020
23021    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23022        .expect("Prepare rename was not started")
23023        .await
23024        .expect("Prepare rename failed");
23025
23026    let mut rename_handler =
23027        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23028            let edit = lsp::TextEdit {
23029                range: lsp::Range {
23030                    start: lsp::Position {
23031                        line: 0,
23032                        character: 7,
23033                    },
23034                    end: lsp::Position {
23035                        line: 0,
23036                        character: 10,
23037                    },
23038                },
23039                new_text: "FooRenamed".to_string(),
23040            };
23041            Ok(Some(lsp::WorkspaceEdit::new(
23042                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
23043            )))
23044        });
23045    let rename_task = cx
23046        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23047        .expect("Confirm rename was not started");
23048    rename_handler.next().await.unwrap();
23049    rename_task.await.expect("Confirm rename failed");
23050    cx.run_until_parked();
23051
23052    // Correct range is renamed, as `surrounding_word` is used to find it.
23053    cx.assert_editor_state(indoc! {"
23054        struct FooRenamedˇ {}
23055    "});
23056}
23057
23058#[gpui::test]
23059async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
23060    init_test(cx, |_| {});
23061    let mut cx = EditorTestContext::new(cx).await;
23062
23063    let language = Arc::new(
23064        Language::new(
23065            LanguageConfig::default(),
23066            Some(tree_sitter_html::LANGUAGE.into()),
23067        )
23068        .with_brackets_query(
23069            r#"
23070            ("<" @open "/>" @close)
23071            ("</" @open ">" @close)
23072            ("<" @open ">" @close)
23073            ("\"" @open "\"" @close)
23074            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
23075        "#,
23076        )
23077        .unwrap(),
23078    );
23079    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23080
23081    cx.set_state(indoc! {"
23082        <span>ˇ</span>
23083    "});
23084    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23085    cx.assert_editor_state(indoc! {"
23086        <span>
23087        ˇ
23088        </span>
23089    "});
23090
23091    cx.set_state(indoc! {"
23092        <span><span></span>ˇ</span>
23093    "});
23094    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23095    cx.assert_editor_state(indoc! {"
23096        <span><span></span>
23097        ˇ</span>
23098    "});
23099
23100    cx.set_state(indoc! {"
23101        <span>ˇ
23102        </span>
23103    "});
23104    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23105    cx.assert_editor_state(indoc! {"
23106        <span>
23107        ˇ
23108        </span>
23109    "});
23110}
23111
23112#[gpui::test(iterations = 10)]
23113async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
23114    init_test(cx, |_| {});
23115
23116    let fs = FakeFs::new(cx.executor());
23117    fs.insert_tree(
23118        path!("/dir"),
23119        json!({
23120            "a.ts": "a",
23121        }),
23122    )
23123    .await;
23124
23125    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
23126    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23127    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23128
23129    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23130    language_registry.add(Arc::new(Language::new(
23131        LanguageConfig {
23132            name: "TypeScript".into(),
23133            matcher: LanguageMatcher {
23134                path_suffixes: vec!["ts".to_string()],
23135                ..Default::default()
23136            },
23137            ..Default::default()
23138        },
23139        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
23140    )));
23141    let mut fake_language_servers = language_registry.register_fake_lsp(
23142        "TypeScript",
23143        FakeLspAdapter {
23144            capabilities: lsp::ServerCapabilities {
23145                code_lens_provider: Some(lsp::CodeLensOptions {
23146                    resolve_provider: Some(true),
23147                }),
23148                execute_command_provider: Some(lsp::ExecuteCommandOptions {
23149                    commands: vec!["_the/command".to_string()],
23150                    ..lsp::ExecuteCommandOptions::default()
23151                }),
23152                ..lsp::ServerCapabilities::default()
23153            },
23154            ..FakeLspAdapter::default()
23155        },
23156    );
23157
23158    let editor = workspace
23159        .update(cx, |workspace, window, cx| {
23160            workspace.open_abs_path(
23161                PathBuf::from(path!("/dir/a.ts")),
23162                OpenOptions::default(),
23163                window,
23164                cx,
23165            )
23166        })
23167        .unwrap()
23168        .await
23169        .unwrap()
23170        .downcast::<Editor>()
23171        .unwrap();
23172    cx.executor().run_until_parked();
23173
23174    let fake_server = fake_language_servers.next().await.unwrap();
23175
23176    let buffer = editor.update(cx, |editor, cx| {
23177        editor
23178            .buffer()
23179            .read(cx)
23180            .as_singleton()
23181            .expect("have opened a single file by path")
23182    });
23183
23184    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
23185    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
23186    drop(buffer_snapshot);
23187    let actions = cx
23188        .update_window(*workspace, |_, window, cx| {
23189            project.code_actions(&buffer, anchor..anchor, window, cx)
23190        })
23191        .unwrap();
23192
23193    fake_server
23194        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23195            Ok(Some(vec![
23196                lsp::CodeLens {
23197                    range: lsp::Range::default(),
23198                    command: Some(lsp::Command {
23199                        title: "Code lens command".to_owned(),
23200                        command: "_the/command".to_owned(),
23201                        arguments: None,
23202                    }),
23203                    data: None,
23204                },
23205                lsp::CodeLens {
23206                    range: lsp::Range::default(),
23207                    command: Some(lsp::Command {
23208                        title: "Command not in capabilities".to_owned(),
23209                        command: "not in capabilities".to_owned(),
23210                        arguments: None,
23211                    }),
23212                    data: None,
23213                },
23214                lsp::CodeLens {
23215                    range: lsp::Range {
23216                        start: lsp::Position {
23217                            line: 1,
23218                            character: 1,
23219                        },
23220                        end: lsp::Position {
23221                            line: 1,
23222                            character: 1,
23223                        },
23224                    },
23225                    command: Some(lsp::Command {
23226                        title: "Command not in range".to_owned(),
23227                        command: "_the/command".to_owned(),
23228                        arguments: None,
23229                    }),
23230                    data: None,
23231                },
23232            ]))
23233        })
23234        .next()
23235        .await;
23236
23237    let actions = actions.await.unwrap();
23238    assert_eq!(
23239        actions.len(),
23240        1,
23241        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
23242    );
23243    let action = actions[0].clone();
23244    let apply = project.update(cx, |project, cx| {
23245        project.apply_code_action(buffer.clone(), action, true, cx)
23246    });
23247
23248    // Resolving the code action does not populate its edits. In absence of
23249    // edits, we must execute the given command.
23250    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
23251        |mut lens, _| async move {
23252            let lens_command = lens.command.as_mut().expect("should have a command");
23253            assert_eq!(lens_command.title, "Code lens command");
23254            lens_command.arguments = Some(vec![json!("the-argument")]);
23255            Ok(lens)
23256        },
23257    );
23258
23259    // While executing the command, the language server sends the editor
23260    // a `workspaceEdit` request.
23261    fake_server
23262        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
23263            let fake = fake_server.clone();
23264            move |params, _| {
23265                assert_eq!(params.command, "_the/command");
23266                let fake = fake.clone();
23267                async move {
23268                    fake.server
23269                        .request::<lsp::request::ApplyWorkspaceEdit>(
23270                            lsp::ApplyWorkspaceEditParams {
23271                                label: None,
23272                                edit: lsp::WorkspaceEdit {
23273                                    changes: Some(
23274                                        [(
23275                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
23276                                            vec![lsp::TextEdit {
23277                                                range: lsp::Range::new(
23278                                                    lsp::Position::new(0, 0),
23279                                                    lsp::Position::new(0, 0),
23280                                                ),
23281                                                new_text: "X".into(),
23282                                            }],
23283                                        )]
23284                                        .into_iter()
23285                                        .collect(),
23286                                    ),
23287                                    ..lsp::WorkspaceEdit::default()
23288                                },
23289                            },
23290                        )
23291                        .await
23292                        .into_response()
23293                        .unwrap();
23294                    Ok(Some(json!(null)))
23295                }
23296            }
23297        })
23298        .next()
23299        .await;
23300
23301    // Applying the code lens command returns a project transaction containing the edits
23302    // sent by the language server in its `workspaceEdit` request.
23303    let transaction = apply.await.unwrap();
23304    assert!(transaction.0.contains_key(&buffer));
23305    buffer.update(cx, |buffer, cx| {
23306        assert_eq!(buffer.text(), "Xa");
23307        buffer.undo(cx);
23308        assert_eq!(buffer.text(), "a");
23309    });
23310
23311    let actions_after_edits = cx
23312        .update_window(*workspace, |_, window, cx| {
23313            project.code_actions(&buffer, anchor..anchor, window, cx)
23314        })
23315        .unwrap()
23316        .await
23317        .unwrap();
23318    assert_eq!(
23319        actions, actions_after_edits,
23320        "For the same selection, same code lens actions should be returned"
23321    );
23322
23323    let _responses =
23324        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23325            panic!("No more code lens requests are expected");
23326        });
23327    editor.update_in(cx, |editor, window, cx| {
23328        editor.select_all(&SelectAll, window, cx);
23329    });
23330    cx.executor().run_until_parked();
23331    let new_actions = cx
23332        .update_window(*workspace, |_, window, cx| {
23333            project.code_actions(&buffer, anchor..anchor, window, cx)
23334        })
23335        .unwrap()
23336        .await
23337        .unwrap();
23338    assert_eq!(
23339        actions, new_actions,
23340        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
23341    );
23342}
23343
23344#[gpui::test]
23345async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
23346    init_test(cx, |_| {});
23347
23348    let fs = FakeFs::new(cx.executor());
23349    let main_text = r#"fn main() {
23350println!("1");
23351println!("2");
23352println!("3");
23353println!("4");
23354println!("5");
23355}"#;
23356    let lib_text = "mod foo {}";
23357    fs.insert_tree(
23358        path!("/a"),
23359        json!({
23360            "lib.rs": lib_text,
23361            "main.rs": main_text,
23362        }),
23363    )
23364    .await;
23365
23366    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23367    let (workspace, cx) =
23368        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23369    let worktree_id = workspace.update(cx, |workspace, cx| {
23370        workspace.project().update(cx, |project, cx| {
23371            project.worktrees(cx).next().unwrap().read(cx).id()
23372        })
23373    });
23374
23375    let expected_ranges = vec![
23376        Point::new(0, 0)..Point::new(0, 0),
23377        Point::new(1, 0)..Point::new(1, 1),
23378        Point::new(2, 0)..Point::new(2, 2),
23379        Point::new(3, 0)..Point::new(3, 3),
23380    ];
23381
23382    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23383    let editor_1 = workspace
23384        .update_in(cx, |workspace, window, cx| {
23385            workspace.open_path(
23386                (worktree_id, "main.rs"),
23387                Some(pane_1.downgrade()),
23388                true,
23389                window,
23390                cx,
23391            )
23392        })
23393        .unwrap()
23394        .await
23395        .downcast::<Editor>()
23396        .unwrap();
23397    pane_1.update(cx, |pane, cx| {
23398        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23399        open_editor.update(cx, |editor, cx| {
23400            assert_eq!(
23401                editor.display_text(cx),
23402                main_text,
23403                "Original main.rs text on initial open",
23404            );
23405            assert_eq!(
23406                editor
23407                    .selections
23408                    .all::<Point>(cx)
23409                    .into_iter()
23410                    .map(|s| s.range())
23411                    .collect::<Vec<_>>(),
23412                vec![Point::zero()..Point::zero()],
23413                "Default selections on initial open",
23414            );
23415        })
23416    });
23417    editor_1.update_in(cx, |editor, window, cx| {
23418        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23419            s.select_ranges(expected_ranges.clone());
23420        });
23421    });
23422
23423    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
23424        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
23425    });
23426    let editor_2 = workspace
23427        .update_in(cx, |workspace, window, cx| {
23428            workspace.open_path(
23429                (worktree_id, "main.rs"),
23430                Some(pane_2.downgrade()),
23431                true,
23432                window,
23433                cx,
23434            )
23435        })
23436        .unwrap()
23437        .await
23438        .downcast::<Editor>()
23439        .unwrap();
23440    pane_2.update(cx, |pane, cx| {
23441        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23442        open_editor.update(cx, |editor, cx| {
23443            assert_eq!(
23444                editor.display_text(cx),
23445                main_text,
23446                "Original main.rs text on initial open in another panel",
23447            );
23448            assert_eq!(
23449                editor
23450                    .selections
23451                    .all::<Point>(cx)
23452                    .into_iter()
23453                    .map(|s| s.range())
23454                    .collect::<Vec<_>>(),
23455                vec![Point::zero()..Point::zero()],
23456                "Default selections on initial open in another panel",
23457            );
23458        })
23459    });
23460
23461    editor_2.update_in(cx, |editor, window, cx| {
23462        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
23463    });
23464
23465    let _other_editor_1 = workspace
23466        .update_in(cx, |workspace, window, cx| {
23467            workspace.open_path(
23468                (worktree_id, "lib.rs"),
23469                Some(pane_1.downgrade()),
23470                true,
23471                window,
23472                cx,
23473            )
23474        })
23475        .unwrap()
23476        .await
23477        .downcast::<Editor>()
23478        .unwrap();
23479    pane_1
23480        .update_in(cx, |pane, window, cx| {
23481            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23482        })
23483        .await
23484        .unwrap();
23485    drop(editor_1);
23486    pane_1.update(cx, |pane, cx| {
23487        pane.active_item()
23488            .unwrap()
23489            .downcast::<Editor>()
23490            .unwrap()
23491            .update(cx, |editor, cx| {
23492                assert_eq!(
23493                    editor.display_text(cx),
23494                    lib_text,
23495                    "Other file should be open and active",
23496                );
23497            });
23498        assert_eq!(pane.items().count(), 1, "No other editors should be open");
23499    });
23500
23501    let _other_editor_2 = workspace
23502        .update_in(cx, |workspace, window, cx| {
23503            workspace.open_path(
23504                (worktree_id, "lib.rs"),
23505                Some(pane_2.downgrade()),
23506                true,
23507                window,
23508                cx,
23509            )
23510        })
23511        .unwrap()
23512        .await
23513        .downcast::<Editor>()
23514        .unwrap();
23515    pane_2
23516        .update_in(cx, |pane, window, cx| {
23517            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23518        })
23519        .await
23520        .unwrap();
23521    drop(editor_2);
23522    pane_2.update(cx, |pane, cx| {
23523        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23524        open_editor.update(cx, |editor, cx| {
23525            assert_eq!(
23526                editor.display_text(cx),
23527                lib_text,
23528                "Other file should be open and active in another panel too",
23529            );
23530        });
23531        assert_eq!(
23532            pane.items().count(),
23533            1,
23534            "No other editors should be open in another pane",
23535        );
23536    });
23537
23538    let _editor_1_reopened = workspace
23539        .update_in(cx, |workspace, window, cx| {
23540            workspace.open_path(
23541                (worktree_id, "main.rs"),
23542                Some(pane_1.downgrade()),
23543                true,
23544                window,
23545                cx,
23546            )
23547        })
23548        .unwrap()
23549        .await
23550        .downcast::<Editor>()
23551        .unwrap();
23552    let _editor_2_reopened = workspace
23553        .update_in(cx, |workspace, window, cx| {
23554            workspace.open_path(
23555                (worktree_id, "main.rs"),
23556                Some(pane_2.downgrade()),
23557                true,
23558                window,
23559                cx,
23560            )
23561        })
23562        .unwrap()
23563        .await
23564        .downcast::<Editor>()
23565        .unwrap();
23566    pane_1.update(cx, |pane, cx| {
23567        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23568        open_editor.update(cx, |editor, cx| {
23569            assert_eq!(
23570                editor.display_text(cx),
23571                main_text,
23572                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
23573            );
23574            assert_eq!(
23575                editor
23576                    .selections
23577                    .all::<Point>(cx)
23578                    .into_iter()
23579                    .map(|s| s.range())
23580                    .collect::<Vec<_>>(),
23581                expected_ranges,
23582                "Previous editor in the 1st panel had selections and should get them restored on reopen",
23583            );
23584        })
23585    });
23586    pane_2.update(cx, |pane, cx| {
23587        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23588        open_editor.update(cx, |editor, cx| {
23589            assert_eq!(
23590                editor.display_text(cx),
23591                r#"fn main() {
23592⋯rintln!("1");
23593⋯intln!("2");
23594⋯ntln!("3");
23595println!("4");
23596println!("5");
23597}"#,
23598                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
23599            );
23600            assert_eq!(
23601                editor
23602                    .selections
23603                    .all::<Point>(cx)
23604                    .into_iter()
23605                    .map(|s| s.range())
23606                    .collect::<Vec<_>>(),
23607                vec![Point::zero()..Point::zero()],
23608                "Previous editor in the 2nd pane had no selections changed hence should restore none",
23609            );
23610        })
23611    });
23612}
23613
23614#[gpui::test]
23615async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
23616    init_test(cx, |_| {});
23617
23618    let fs = FakeFs::new(cx.executor());
23619    let main_text = r#"fn main() {
23620println!("1");
23621println!("2");
23622println!("3");
23623println!("4");
23624println!("5");
23625}"#;
23626    let lib_text = "mod foo {}";
23627    fs.insert_tree(
23628        path!("/a"),
23629        json!({
23630            "lib.rs": lib_text,
23631            "main.rs": main_text,
23632        }),
23633    )
23634    .await;
23635
23636    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23637    let (workspace, cx) =
23638        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23639    let worktree_id = workspace.update(cx, |workspace, cx| {
23640        workspace.project().update(cx, |project, cx| {
23641            project.worktrees(cx).next().unwrap().read(cx).id()
23642        })
23643    });
23644
23645    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23646    let editor = workspace
23647        .update_in(cx, |workspace, window, cx| {
23648            workspace.open_path(
23649                (worktree_id, "main.rs"),
23650                Some(pane.downgrade()),
23651                true,
23652                window,
23653                cx,
23654            )
23655        })
23656        .unwrap()
23657        .await
23658        .downcast::<Editor>()
23659        .unwrap();
23660    pane.update(cx, |pane, cx| {
23661        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23662        open_editor.update(cx, |editor, cx| {
23663            assert_eq!(
23664                editor.display_text(cx),
23665                main_text,
23666                "Original main.rs text on initial open",
23667            );
23668        })
23669    });
23670    editor.update_in(cx, |editor, window, cx| {
23671        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
23672    });
23673
23674    cx.update_global(|store: &mut SettingsStore, cx| {
23675        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
23676            s.restore_on_file_reopen = Some(false);
23677        });
23678    });
23679    editor.update_in(cx, |editor, window, cx| {
23680        editor.fold_ranges(
23681            vec![
23682                Point::new(1, 0)..Point::new(1, 1),
23683                Point::new(2, 0)..Point::new(2, 2),
23684                Point::new(3, 0)..Point::new(3, 3),
23685            ],
23686            false,
23687            window,
23688            cx,
23689        );
23690    });
23691    pane.update_in(cx, |pane, window, cx| {
23692        pane.close_all_items(&CloseAllItems::default(), window, cx)
23693    })
23694    .await
23695    .unwrap();
23696    pane.update(cx, |pane, _| {
23697        assert!(pane.active_item().is_none());
23698    });
23699    cx.update_global(|store: &mut SettingsStore, cx| {
23700        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
23701            s.restore_on_file_reopen = Some(true);
23702        });
23703    });
23704
23705    let _editor_reopened = workspace
23706        .update_in(cx, |workspace, window, cx| {
23707            workspace.open_path(
23708                (worktree_id, "main.rs"),
23709                Some(pane.downgrade()),
23710                true,
23711                window,
23712                cx,
23713            )
23714        })
23715        .unwrap()
23716        .await
23717        .downcast::<Editor>()
23718        .unwrap();
23719    pane.update(cx, |pane, cx| {
23720        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23721        open_editor.update(cx, |editor, cx| {
23722            assert_eq!(
23723                editor.display_text(cx),
23724                main_text,
23725                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
23726            );
23727        })
23728    });
23729}
23730
23731#[gpui::test]
23732async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
23733    struct EmptyModalView {
23734        focus_handle: gpui::FocusHandle,
23735    }
23736    impl EventEmitter<DismissEvent> for EmptyModalView {}
23737    impl Render for EmptyModalView {
23738        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
23739            div()
23740        }
23741    }
23742    impl Focusable for EmptyModalView {
23743        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
23744            self.focus_handle.clone()
23745        }
23746    }
23747    impl workspace::ModalView for EmptyModalView {}
23748    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
23749        EmptyModalView {
23750            focus_handle: cx.focus_handle(),
23751        }
23752    }
23753
23754    init_test(cx, |_| {});
23755
23756    let fs = FakeFs::new(cx.executor());
23757    let project = Project::test(fs, [], cx).await;
23758    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23759    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
23760    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23761    let editor = cx.new_window_entity(|window, cx| {
23762        Editor::new(
23763            EditorMode::full(),
23764            buffer,
23765            Some(project.clone()),
23766            window,
23767            cx,
23768        )
23769    });
23770    workspace
23771        .update(cx, |workspace, window, cx| {
23772            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
23773        })
23774        .unwrap();
23775    editor.update_in(cx, |editor, window, cx| {
23776        editor.open_context_menu(&OpenContextMenu, window, cx);
23777        assert!(editor.mouse_context_menu.is_some());
23778    });
23779    workspace
23780        .update(cx, |workspace, window, cx| {
23781            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
23782        })
23783        .unwrap();
23784    cx.read(|cx| {
23785        assert!(editor.read(cx).mouse_context_menu.is_none());
23786    });
23787}
23788
23789#[gpui::test]
23790async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
23791    init_test(cx, |_| {});
23792
23793    let fs = FakeFs::new(cx.executor());
23794    fs.insert_file(path!("/file.html"), Default::default())
23795        .await;
23796
23797    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
23798
23799    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23800    let html_language = Arc::new(Language::new(
23801        LanguageConfig {
23802            name: "HTML".into(),
23803            matcher: LanguageMatcher {
23804                path_suffixes: vec!["html".to_string()],
23805                ..LanguageMatcher::default()
23806            },
23807            brackets: BracketPairConfig {
23808                pairs: vec![BracketPair {
23809                    start: "<".into(),
23810                    end: ">".into(),
23811                    close: true,
23812                    ..Default::default()
23813                }],
23814                ..Default::default()
23815            },
23816            ..Default::default()
23817        },
23818        Some(tree_sitter_html::LANGUAGE.into()),
23819    ));
23820    language_registry.add(html_language);
23821    let mut fake_servers = language_registry.register_fake_lsp(
23822        "HTML",
23823        FakeLspAdapter {
23824            capabilities: lsp::ServerCapabilities {
23825                completion_provider: Some(lsp::CompletionOptions {
23826                    resolve_provider: Some(true),
23827                    ..Default::default()
23828                }),
23829                ..Default::default()
23830            },
23831            ..Default::default()
23832        },
23833    );
23834
23835    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23836    let cx = &mut VisualTestContext::from_window(*workspace, cx);
23837
23838    let worktree_id = workspace
23839        .update(cx, |workspace, _window, cx| {
23840            workspace.project().update(cx, |project, cx| {
23841                project.worktrees(cx).next().unwrap().read(cx).id()
23842            })
23843        })
23844        .unwrap();
23845    project
23846        .update(cx, |project, cx| {
23847            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
23848        })
23849        .await
23850        .unwrap();
23851    let editor = workspace
23852        .update(cx, |workspace, window, cx| {
23853            workspace.open_path((worktree_id, "file.html"), None, true, window, cx)
23854        })
23855        .unwrap()
23856        .await
23857        .unwrap()
23858        .downcast::<Editor>()
23859        .unwrap();
23860
23861    let fake_server = fake_servers.next().await.unwrap();
23862    editor.update_in(cx, |editor, window, cx| {
23863        editor.set_text("<ad></ad>", window, cx);
23864        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23865            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
23866        });
23867        let Some((buffer, _)) = editor
23868            .buffer
23869            .read(cx)
23870            .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
23871        else {
23872            panic!("Failed to get buffer for selection position");
23873        };
23874        let buffer = buffer.read(cx);
23875        let buffer_id = buffer.remote_id();
23876        let opening_range =
23877            buffer.anchor_before(Point::new(0, 1))..buffer.anchor_after(Point::new(0, 3));
23878        let closing_range =
23879            buffer.anchor_before(Point::new(0, 6))..buffer.anchor_after(Point::new(0, 8));
23880        let mut linked_ranges = HashMap::default();
23881        linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
23882        editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
23883    });
23884    let mut completion_handle =
23885        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
23886            Ok(Some(lsp::CompletionResponse::Array(vec![
23887                lsp::CompletionItem {
23888                    label: "head".to_string(),
23889                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23890                        lsp::InsertReplaceEdit {
23891                            new_text: "head".to_string(),
23892                            insert: lsp::Range::new(
23893                                lsp::Position::new(0, 1),
23894                                lsp::Position::new(0, 3),
23895                            ),
23896                            replace: lsp::Range::new(
23897                                lsp::Position::new(0, 1),
23898                                lsp::Position::new(0, 3),
23899                            ),
23900                        },
23901                    )),
23902                    ..Default::default()
23903                },
23904            ])))
23905        });
23906    editor.update_in(cx, |editor, window, cx| {
23907        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
23908    });
23909    cx.run_until_parked();
23910    completion_handle.next().await.unwrap();
23911    editor.update(cx, |editor, _| {
23912        assert!(
23913            editor.context_menu_visible(),
23914            "Completion menu should be visible"
23915        );
23916    });
23917    editor.update_in(cx, |editor, window, cx| {
23918        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
23919    });
23920    cx.executor().run_until_parked();
23921    editor.update(cx, |editor, cx| {
23922        assert_eq!(editor.text(cx), "<head></head>");
23923    });
23924}
23925
23926#[gpui::test]
23927async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
23928    init_test(cx, |_| {});
23929
23930    let fs = FakeFs::new(cx.executor());
23931    fs.insert_tree(
23932        path!("/root"),
23933        json!({
23934            "a": {
23935                "main.rs": "fn main() {}",
23936            },
23937            "foo": {
23938                "bar": {
23939                    "external_file.rs": "pub mod external {}",
23940                }
23941            }
23942        }),
23943    )
23944    .await;
23945
23946    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
23947    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23948    language_registry.add(rust_lang());
23949    let _fake_servers = language_registry.register_fake_lsp(
23950        "Rust",
23951        FakeLspAdapter {
23952            ..FakeLspAdapter::default()
23953        },
23954    );
23955    let (workspace, cx) =
23956        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23957    let worktree_id = workspace.update(cx, |workspace, cx| {
23958        workspace.project().update(cx, |project, cx| {
23959            project.worktrees(cx).next().unwrap().read(cx).id()
23960        })
23961    });
23962
23963    let assert_language_servers_count =
23964        |expected: usize, context: &str, cx: &mut VisualTestContext| {
23965            project.update(cx, |project, cx| {
23966                let current = project
23967                    .lsp_store()
23968                    .read(cx)
23969                    .as_local()
23970                    .unwrap()
23971                    .language_servers
23972                    .len();
23973                assert_eq!(expected, current, "{context}");
23974            });
23975        };
23976
23977    assert_language_servers_count(
23978        0,
23979        "No servers should be running before any file is open",
23980        cx,
23981    );
23982    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23983    let main_editor = workspace
23984        .update_in(cx, |workspace, window, cx| {
23985            workspace.open_path(
23986                (worktree_id, "main.rs"),
23987                Some(pane.downgrade()),
23988                true,
23989                window,
23990                cx,
23991            )
23992        })
23993        .unwrap()
23994        .await
23995        .downcast::<Editor>()
23996        .unwrap();
23997    pane.update(cx, |pane, cx| {
23998        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23999        open_editor.update(cx, |editor, cx| {
24000            assert_eq!(
24001                editor.display_text(cx),
24002                "fn main() {}",
24003                "Original main.rs text on initial open",
24004            );
24005        });
24006        assert_eq!(open_editor, main_editor);
24007    });
24008    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
24009
24010    let external_editor = workspace
24011        .update_in(cx, |workspace, window, cx| {
24012            workspace.open_abs_path(
24013                PathBuf::from("/root/foo/bar/external_file.rs"),
24014                OpenOptions::default(),
24015                window,
24016                cx,
24017            )
24018        })
24019        .await
24020        .expect("opening external file")
24021        .downcast::<Editor>()
24022        .expect("downcasted external file's open element to editor");
24023    pane.update(cx, |pane, cx| {
24024        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24025        open_editor.update(cx, |editor, cx| {
24026            assert_eq!(
24027                editor.display_text(cx),
24028                "pub mod external {}",
24029                "External file is open now",
24030            );
24031        });
24032        assert_eq!(open_editor, external_editor);
24033    });
24034    assert_language_servers_count(
24035        1,
24036        "Second, external, *.rs file should join the existing server",
24037        cx,
24038    );
24039
24040    pane.update_in(cx, |pane, window, cx| {
24041        pane.close_active_item(&CloseActiveItem::default(), window, cx)
24042    })
24043    .await
24044    .unwrap();
24045    pane.update_in(cx, |pane, window, cx| {
24046        pane.navigate_backward(&Default::default(), window, cx);
24047    });
24048    cx.run_until_parked();
24049    pane.update(cx, |pane, cx| {
24050        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24051        open_editor.update(cx, |editor, cx| {
24052            assert_eq!(
24053                editor.display_text(cx),
24054                "pub mod external {}",
24055                "External file is open now",
24056            );
24057        });
24058    });
24059    assert_language_servers_count(
24060        1,
24061        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
24062        cx,
24063    );
24064
24065    cx.update(|_, cx| {
24066        workspace::reload(cx);
24067    });
24068    assert_language_servers_count(
24069        1,
24070        "After reloading the worktree with local and external files opened, only one project should be started",
24071        cx,
24072    );
24073}
24074
24075#[gpui::test]
24076async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
24077    init_test(cx, |_| {});
24078
24079    let mut cx = EditorTestContext::new(cx).await;
24080    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24081    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24082
24083    // test cursor move to start of each line on tab
24084    // for `if`, `elif`, `else`, `while`, `with` and `for`
24085    cx.set_state(indoc! {"
24086        def main():
24087        ˇ    for item in items:
24088        ˇ        while item.active:
24089        ˇ            if item.value > 10:
24090        ˇ                continue
24091        ˇ            elif item.value < 0:
24092        ˇ                break
24093        ˇ            else:
24094        ˇ                with item.context() as ctx:
24095        ˇ                    yield count
24096        ˇ        else:
24097        ˇ            log('while else')
24098        ˇ    else:
24099        ˇ        log('for else')
24100    "});
24101    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24102    cx.assert_editor_state(indoc! {"
24103        def main():
24104            ˇfor item in items:
24105                ˇwhile item.active:
24106                    ˇif item.value > 10:
24107                        ˇcontinue
24108                    ˇelif item.value < 0:
24109                        ˇbreak
24110                    ˇelse:
24111                        ˇwith item.context() as ctx:
24112                            ˇyield count
24113                ˇelse:
24114                    ˇlog('while else')
24115            ˇelse:
24116                ˇlog('for else')
24117    "});
24118    // test relative indent is preserved when tab
24119    // for `if`, `elif`, `else`, `while`, `with` and `for`
24120    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24121    cx.assert_editor_state(indoc! {"
24122        def main():
24123                ˇfor item in items:
24124                    ˇwhile item.active:
24125                        ˇif item.value > 10:
24126                            ˇcontinue
24127                        ˇelif item.value < 0:
24128                            ˇbreak
24129                        ˇelse:
24130                            ˇwith item.context() as ctx:
24131                                ˇyield count
24132                    ˇelse:
24133                        ˇlog('while else')
24134                ˇelse:
24135                    ˇlog('for else')
24136    "});
24137
24138    // test cursor move to start of each line on tab
24139    // for `try`, `except`, `else`, `finally`, `match` and `def`
24140    cx.set_state(indoc! {"
24141        def main():
24142        ˇ    try:
24143        ˇ        fetch()
24144        ˇ    except ValueError:
24145        ˇ        handle_error()
24146        ˇ    else:
24147        ˇ        match value:
24148        ˇ            case _:
24149        ˇ    finally:
24150        ˇ        def status():
24151        ˇ            return 0
24152    "});
24153    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24154    cx.assert_editor_state(indoc! {"
24155        def main():
24156            ˇtry:
24157                ˇfetch()
24158            ˇexcept ValueError:
24159                ˇhandle_error()
24160            ˇelse:
24161                ˇmatch value:
24162                    ˇcase _:
24163            ˇfinally:
24164                ˇdef status():
24165                    ˇreturn 0
24166    "});
24167    // test relative indent is preserved when tab
24168    // for `try`, `except`, `else`, `finally`, `match` and `def`
24169    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24170    cx.assert_editor_state(indoc! {"
24171        def main():
24172                ˇtry:
24173                    ˇfetch()
24174                ˇexcept ValueError:
24175                    ˇhandle_error()
24176                ˇelse:
24177                    ˇmatch value:
24178                        ˇcase _:
24179                ˇfinally:
24180                    ˇdef status():
24181                        ˇreturn 0
24182    "});
24183}
24184
24185#[gpui::test]
24186async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
24187    init_test(cx, |_| {});
24188
24189    let mut cx = EditorTestContext::new(cx).await;
24190    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24191    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24192
24193    // test `else` auto outdents when typed inside `if` block
24194    cx.set_state(indoc! {"
24195        def main():
24196            if i == 2:
24197                return
24198                ˇ
24199    "});
24200    cx.update_editor(|editor, window, cx| {
24201        editor.handle_input("else:", window, cx);
24202    });
24203    cx.assert_editor_state(indoc! {"
24204        def main():
24205            if i == 2:
24206                return
24207            else:ˇ
24208    "});
24209
24210    // test `except` auto outdents when typed inside `try` block
24211    cx.set_state(indoc! {"
24212        def main():
24213            try:
24214                i = 2
24215                ˇ
24216    "});
24217    cx.update_editor(|editor, window, cx| {
24218        editor.handle_input("except:", window, cx);
24219    });
24220    cx.assert_editor_state(indoc! {"
24221        def main():
24222            try:
24223                i = 2
24224            except:ˇ
24225    "});
24226
24227    // test `else` auto outdents when typed inside `except` block
24228    cx.set_state(indoc! {"
24229        def main():
24230            try:
24231                i = 2
24232            except:
24233                j = 2
24234                ˇ
24235    "});
24236    cx.update_editor(|editor, window, cx| {
24237        editor.handle_input("else:", window, cx);
24238    });
24239    cx.assert_editor_state(indoc! {"
24240        def main():
24241            try:
24242                i = 2
24243            except:
24244                j = 2
24245            else:ˇ
24246    "});
24247
24248    // test `finally` auto outdents when typed inside `else` block
24249    cx.set_state(indoc! {"
24250        def main():
24251            try:
24252                i = 2
24253            except:
24254                j = 2
24255            else:
24256                k = 2
24257                ˇ
24258    "});
24259    cx.update_editor(|editor, window, cx| {
24260        editor.handle_input("finally:", window, cx);
24261    });
24262    cx.assert_editor_state(indoc! {"
24263        def main():
24264            try:
24265                i = 2
24266            except:
24267                j = 2
24268            else:
24269                k = 2
24270            finally:ˇ
24271    "});
24272
24273    // test `else` does not outdents when typed inside `except` block right after for block
24274    cx.set_state(indoc! {"
24275        def main():
24276            try:
24277                i = 2
24278            except:
24279                for i in range(n):
24280                    pass
24281                ˇ
24282    "});
24283    cx.update_editor(|editor, window, cx| {
24284        editor.handle_input("else:", window, cx);
24285    });
24286    cx.assert_editor_state(indoc! {"
24287        def main():
24288            try:
24289                i = 2
24290            except:
24291                for i in range(n):
24292                    pass
24293                else:ˇ
24294    "});
24295
24296    // test `finally` auto outdents when typed inside `else` block right after for block
24297    cx.set_state(indoc! {"
24298        def main():
24299            try:
24300                i = 2
24301            except:
24302                j = 2
24303            else:
24304                for i in range(n):
24305                    pass
24306                ˇ
24307    "});
24308    cx.update_editor(|editor, window, cx| {
24309        editor.handle_input("finally:", window, cx);
24310    });
24311    cx.assert_editor_state(indoc! {"
24312        def main():
24313            try:
24314                i = 2
24315            except:
24316                j = 2
24317            else:
24318                for i in range(n):
24319                    pass
24320            finally:ˇ
24321    "});
24322
24323    // test `except` outdents to inner "try" block
24324    cx.set_state(indoc! {"
24325        def main():
24326            try:
24327                i = 2
24328                if i == 2:
24329                    try:
24330                        i = 3
24331                        ˇ
24332    "});
24333    cx.update_editor(|editor, window, cx| {
24334        editor.handle_input("except:", window, cx);
24335    });
24336    cx.assert_editor_state(indoc! {"
24337        def main():
24338            try:
24339                i = 2
24340                if i == 2:
24341                    try:
24342                        i = 3
24343                    except:ˇ
24344    "});
24345
24346    // test `except` outdents to outer "try" block
24347    cx.set_state(indoc! {"
24348        def main():
24349            try:
24350                i = 2
24351                if i == 2:
24352                    try:
24353                        i = 3
24354                ˇ
24355    "});
24356    cx.update_editor(|editor, window, cx| {
24357        editor.handle_input("except:", window, cx);
24358    });
24359    cx.assert_editor_state(indoc! {"
24360        def main():
24361            try:
24362                i = 2
24363                if i == 2:
24364                    try:
24365                        i = 3
24366            except:ˇ
24367    "});
24368
24369    // test `else` stays at correct indent when typed after `for` block
24370    cx.set_state(indoc! {"
24371        def main():
24372            for i in range(10):
24373                if i == 3:
24374                    break
24375            ˇ
24376    "});
24377    cx.update_editor(|editor, window, cx| {
24378        editor.handle_input("else:", window, cx);
24379    });
24380    cx.assert_editor_state(indoc! {"
24381        def main():
24382            for i in range(10):
24383                if i == 3:
24384                    break
24385            else:ˇ
24386    "});
24387
24388    // test does not outdent on typing after line with square brackets
24389    cx.set_state(indoc! {"
24390        def f() -> list[str]:
24391            ˇ
24392    "});
24393    cx.update_editor(|editor, window, cx| {
24394        editor.handle_input("a", window, cx);
24395    });
24396    cx.assert_editor_state(indoc! {"
24397        def f() -> list[str]:
2439824399    "});
24400
24401    // test does not outdent on typing : after case keyword
24402    cx.set_state(indoc! {"
24403        match 1:
24404            caseˇ
24405    "});
24406    cx.update_editor(|editor, window, cx| {
24407        editor.handle_input(":", window, cx);
24408    });
24409    cx.assert_editor_state(indoc! {"
24410        match 1:
24411            case:ˇ
24412    "});
24413}
24414
24415#[gpui::test]
24416async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
24417    init_test(cx, |_| {});
24418    update_test_language_settings(cx, |settings| {
24419        settings.defaults.extend_comment_on_newline = Some(false);
24420    });
24421    let mut cx = EditorTestContext::new(cx).await;
24422    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24423    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24424
24425    // test correct indent after newline on comment
24426    cx.set_state(indoc! {"
24427        # COMMENT:ˇ
24428    "});
24429    cx.update_editor(|editor, window, cx| {
24430        editor.newline(&Newline, window, cx);
24431    });
24432    cx.assert_editor_state(indoc! {"
24433        # COMMENT:
24434        ˇ
24435    "});
24436
24437    // test correct indent after newline in brackets
24438    cx.set_state(indoc! {"
24439        {ˇ}
24440    "});
24441    cx.update_editor(|editor, window, cx| {
24442        editor.newline(&Newline, window, cx);
24443    });
24444    cx.run_until_parked();
24445    cx.assert_editor_state(indoc! {"
24446        {
24447            ˇ
24448        }
24449    "});
24450
24451    cx.set_state(indoc! {"
24452        (ˇ)
24453    "});
24454    cx.update_editor(|editor, window, cx| {
24455        editor.newline(&Newline, window, cx);
24456    });
24457    cx.run_until_parked();
24458    cx.assert_editor_state(indoc! {"
24459        (
24460            ˇ
24461        )
24462    "});
24463
24464    // do not indent after empty lists or dictionaries
24465    cx.set_state(indoc! {"
24466        a = []ˇ
24467    "});
24468    cx.update_editor(|editor, window, cx| {
24469        editor.newline(&Newline, window, cx);
24470    });
24471    cx.run_until_parked();
24472    cx.assert_editor_state(indoc! {"
24473        a = []
24474        ˇ
24475    "});
24476}
24477
24478#[gpui::test]
24479async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
24480    init_test(cx, |_| {});
24481
24482    let mut cx = EditorTestContext::new(cx).await;
24483    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24484    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24485
24486    // test cursor move to start of each line on tab
24487    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
24488    cx.set_state(indoc! {"
24489        function main() {
24490        ˇ    for item in $items; do
24491        ˇ        while [ -n \"$item\" ]; do
24492        ˇ            if [ \"$value\" -gt 10 ]; then
24493        ˇ                continue
24494        ˇ            elif [ \"$value\" -lt 0 ]; then
24495        ˇ                break
24496        ˇ            else
24497        ˇ                echo \"$item\"
24498        ˇ            fi
24499        ˇ        done
24500        ˇ    done
24501        ˇ}
24502    "});
24503    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24504    cx.assert_editor_state(indoc! {"
24505        function main() {
24506            ˇfor item in $items; do
24507                ˇwhile [ -n \"$item\" ]; do
24508                    ˇif [ \"$value\" -gt 10 ]; then
24509                        ˇcontinue
24510                    ˇelif [ \"$value\" -lt 0 ]; then
24511                        ˇbreak
24512                    ˇelse
24513                        ˇecho \"$item\"
24514                    ˇfi
24515                ˇdone
24516            ˇdone
24517        ˇ}
24518    "});
24519    // test relative indent is preserved when tab
24520    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24521    cx.assert_editor_state(indoc! {"
24522        function main() {
24523                ˇfor item in $items; do
24524                    ˇwhile [ -n \"$item\" ]; do
24525                        ˇif [ \"$value\" -gt 10 ]; then
24526                            ˇcontinue
24527                        ˇelif [ \"$value\" -lt 0 ]; then
24528                            ˇbreak
24529                        ˇelse
24530                            ˇecho \"$item\"
24531                        ˇfi
24532                    ˇdone
24533                ˇdone
24534            ˇ}
24535    "});
24536
24537    // test cursor move to start of each line on tab
24538    // for `case` statement with patterns
24539    cx.set_state(indoc! {"
24540        function handle() {
24541        ˇ    case \"$1\" in
24542        ˇ        start)
24543        ˇ            echo \"a\"
24544        ˇ            ;;
24545        ˇ        stop)
24546        ˇ            echo \"b\"
24547        ˇ            ;;
24548        ˇ        *)
24549        ˇ            echo \"c\"
24550        ˇ            ;;
24551        ˇ    esac
24552        ˇ}
24553    "});
24554    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24555    cx.assert_editor_state(indoc! {"
24556        function handle() {
24557            ˇcase \"$1\" in
24558                ˇstart)
24559                    ˇecho \"a\"
24560                    ˇ;;
24561                ˇstop)
24562                    ˇecho \"b\"
24563                    ˇ;;
24564                ˇ*)
24565                    ˇecho \"c\"
24566                    ˇ;;
24567            ˇesac
24568        ˇ}
24569    "});
24570}
24571
24572#[gpui::test]
24573async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
24574    init_test(cx, |_| {});
24575
24576    let mut cx = EditorTestContext::new(cx).await;
24577    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24578    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24579
24580    // test indents on comment insert
24581    cx.set_state(indoc! {"
24582        function main() {
24583        ˇ    for item in $items; do
24584        ˇ        while [ -n \"$item\" ]; do
24585        ˇ            if [ \"$value\" -gt 10 ]; then
24586        ˇ                continue
24587        ˇ            elif [ \"$value\" -lt 0 ]; then
24588        ˇ                break
24589        ˇ            else
24590        ˇ                echo \"$item\"
24591        ˇ            fi
24592        ˇ        done
24593        ˇ    done
24594        ˇ}
24595    "});
24596    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
24597    cx.assert_editor_state(indoc! {"
24598        function main() {
24599        #ˇ    for item in $items; do
24600        #ˇ        while [ -n \"$item\" ]; do
24601        #ˇ            if [ \"$value\" -gt 10 ]; then
24602        #ˇ                continue
24603        #ˇ            elif [ \"$value\" -lt 0 ]; then
24604        #ˇ                break
24605        #ˇ            else
24606        #ˇ                echo \"$item\"
24607        #ˇ            fi
24608        #ˇ        done
24609        #ˇ    done
24610        #ˇ}
24611    "});
24612}
24613
24614#[gpui::test]
24615async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
24616    init_test(cx, |_| {});
24617
24618    let mut cx = EditorTestContext::new(cx).await;
24619    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24620    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24621
24622    // test `else` auto outdents when typed inside `if` block
24623    cx.set_state(indoc! {"
24624        if [ \"$1\" = \"test\" ]; then
24625            echo \"foo bar\"
24626            ˇ
24627    "});
24628    cx.update_editor(|editor, window, cx| {
24629        editor.handle_input("else", window, cx);
24630    });
24631    cx.assert_editor_state(indoc! {"
24632        if [ \"$1\" = \"test\" ]; then
24633            echo \"foo bar\"
24634        elseˇ
24635    "});
24636
24637    // test `elif` auto outdents when typed inside `if` block
24638    cx.set_state(indoc! {"
24639        if [ \"$1\" = \"test\" ]; then
24640            echo \"foo bar\"
24641            ˇ
24642    "});
24643    cx.update_editor(|editor, window, cx| {
24644        editor.handle_input("elif", window, cx);
24645    });
24646    cx.assert_editor_state(indoc! {"
24647        if [ \"$1\" = \"test\" ]; then
24648            echo \"foo bar\"
24649        elifˇ
24650    "});
24651
24652    // test `fi` auto outdents when typed inside `else` block
24653    cx.set_state(indoc! {"
24654        if [ \"$1\" = \"test\" ]; then
24655            echo \"foo bar\"
24656        else
24657            echo \"bar baz\"
24658            ˇ
24659    "});
24660    cx.update_editor(|editor, window, cx| {
24661        editor.handle_input("fi", window, cx);
24662    });
24663    cx.assert_editor_state(indoc! {"
24664        if [ \"$1\" = \"test\" ]; then
24665            echo \"foo bar\"
24666        else
24667            echo \"bar baz\"
24668        fiˇ
24669    "});
24670
24671    // test `done` auto outdents when typed inside `while` block
24672    cx.set_state(indoc! {"
24673        while read line; do
24674            echo \"$line\"
24675            ˇ
24676    "});
24677    cx.update_editor(|editor, window, cx| {
24678        editor.handle_input("done", window, cx);
24679    });
24680    cx.assert_editor_state(indoc! {"
24681        while read line; do
24682            echo \"$line\"
24683        doneˇ
24684    "});
24685
24686    // test `done` auto outdents when typed inside `for` block
24687    cx.set_state(indoc! {"
24688        for file in *.txt; do
24689            cat \"$file\"
24690            ˇ
24691    "});
24692    cx.update_editor(|editor, window, cx| {
24693        editor.handle_input("done", window, cx);
24694    });
24695    cx.assert_editor_state(indoc! {"
24696        for file in *.txt; do
24697            cat \"$file\"
24698        doneˇ
24699    "});
24700
24701    // test `esac` auto outdents when typed inside `case` block
24702    cx.set_state(indoc! {"
24703        case \"$1\" in
24704            start)
24705                echo \"foo bar\"
24706                ;;
24707            stop)
24708                echo \"bar baz\"
24709                ;;
24710            ˇ
24711    "});
24712    cx.update_editor(|editor, window, cx| {
24713        editor.handle_input("esac", window, cx);
24714    });
24715    cx.assert_editor_state(indoc! {"
24716        case \"$1\" in
24717            start)
24718                echo \"foo bar\"
24719                ;;
24720            stop)
24721                echo \"bar baz\"
24722                ;;
24723        esacˇ
24724    "});
24725
24726    // test `*)` auto outdents when typed inside `case` block
24727    cx.set_state(indoc! {"
24728        case \"$1\" in
24729            start)
24730                echo \"foo bar\"
24731                ;;
24732                ˇ
24733    "});
24734    cx.update_editor(|editor, window, cx| {
24735        editor.handle_input("*)", window, cx);
24736    });
24737    cx.assert_editor_state(indoc! {"
24738        case \"$1\" in
24739            start)
24740                echo \"foo bar\"
24741                ;;
24742            *)ˇ
24743    "});
24744
24745    // test `fi` outdents to correct level with nested if blocks
24746    cx.set_state(indoc! {"
24747        if [ \"$1\" = \"test\" ]; then
24748            echo \"outer if\"
24749            if [ \"$2\" = \"debug\" ]; then
24750                echo \"inner if\"
24751                ˇ
24752    "});
24753    cx.update_editor(|editor, window, cx| {
24754        editor.handle_input("fi", window, cx);
24755    });
24756    cx.assert_editor_state(indoc! {"
24757        if [ \"$1\" = \"test\" ]; then
24758            echo \"outer if\"
24759            if [ \"$2\" = \"debug\" ]; then
24760                echo \"inner if\"
24761            fiˇ
24762    "});
24763}
24764
24765#[gpui::test]
24766async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
24767    init_test(cx, |_| {});
24768    update_test_language_settings(cx, |settings| {
24769        settings.defaults.extend_comment_on_newline = Some(false);
24770    });
24771    let mut cx = EditorTestContext::new(cx).await;
24772    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24773    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24774
24775    // test correct indent after newline on comment
24776    cx.set_state(indoc! {"
24777        # COMMENT:ˇ
24778    "});
24779    cx.update_editor(|editor, window, cx| {
24780        editor.newline(&Newline, window, cx);
24781    });
24782    cx.assert_editor_state(indoc! {"
24783        # COMMENT:
24784        ˇ
24785    "});
24786
24787    // test correct indent after newline after `then`
24788    cx.set_state(indoc! {"
24789
24790        if [ \"$1\" = \"test\" ]; thenˇ
24791    "});
24792    cx.update_editor(|editor, window, cx| {
24793        editor.newline(&Newline, window, cx);
24794    });
24795    cx.run_until_parked();
24796    cx.assert_editor_state(indoc! {"
24797
24798        if [ \"$1\" = \"test\" ]; then
24799            ˇ
24800    "});
24801
24802    // test correct indent after newline after `else`
24803    cx.set_state(indoc! {"
24804        if [ \"$1\" = \"test\" ]; then
24805        elseˇ
24806    "});
24807    cx.update_editor(|editor, window, cx| {
24808        editor.newline(&Newline, window, cx);
24809    });
24810    cx.run_until_parked();
24811    cx.assert_editor_state(indoc! {"
24812        if [ \"$1\" = \"test\" ]; then
24813        else
24814            ˇ
24815    "});
24816
24817    // test correct indent after newline after `elif`
24818    cx.set_state(indoc! {"
24819        if [ \"$1\" = \"test\" ]; then
24820        elifˇ
24821    "});
24822    cx.update_editor(|editor, window, cx| {
24823        editor.newline(&Newline, window, cx);
24824    });
24825    cx.run_until_parked();
24826    cx.assert_editor_state(indoc! {"
24827        if [ \"$1\" = \"test\" ]; then
24828        elif
24829            ˇ
24830    "});
24831
24832    // test correct indent after newline after `do`
24833    cx.set_state(indoc! {"
24834        for file in *.txt; doˇ
24835    "});
24836    cx.update_editor(|editor, window, cx| {
24837        editor.newline(&Newline, window, cx);
24838    });
24839    cx.run_until_parked();
24840    cx.assert_editor_state(indoc! {"
24841        for file in *.txt; do
24842            ˇ
24843    "});
24844
24845    // test correct indent after newline after case pattern
24846    cx.set_state(indoc! {"
24847        case \"$1\" in
24848            start)ˇ
24849    "});
24850    cx.update_editor(|editor, window, cx| {
24851        editor.newline(&Newline, window, cx);
24852    });
24853    cx.run_until_parked();
24854    cx.assert_editor_state(indoc! {"
24855        case \"$1\" in
24856            start)
24857                ˇ
24858    "});
24859
24860    // test correct indent after newline after case pattern
24861    cx.set_state(indoc! {"
24862        case \"$1\" in
24863            start)
24864                ;;
24865            *)ˇ
24866    "});
24867    cx.update_editor(|editor, window, cx| {
24868        editor.newline(&Newline, window, cx);
24869    });
24870    cx.run_until_parked();
24871    cx.assert_editor_state(indoc! {"
24872        case \"$1\" in
24873            start)
24874                ;;
24875            *)
24876                ˇ
24877    "});
24878
24879    // test correct indent after newline after function opening brace
24880    cx.set_state(indoc! {"
24881        function test() {ˇ}
24882    "});
24883    cx.update_editor(|editor, window, cx| {
24884        editor.newline(&Newline, window, cx);
24885    });
24886    cx.run_until_parked();
24887    cx.assert_editor_state(indoc! {"
24888        function test() {
24889            ˇ
24890        }
24891    "});
24892
24893    // test no extra indent after semicolon on same line
24894    cx.set_state(indoc! {"
24895        echo \"test\"24896    "});
24897    cx.update_editor(|editor, window, cx| {
24898        editor.newline(&Newline, window, cx);
24899    });
24900    cx.run_until_parked();
24901    cx.assert_editor_state(indoc! {"
24902        echo \"test\";
24903        ˇ
24904    "});
24905}
24906
24907fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
24908    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
24909    point..point
24910}
24911
24912#[track_caller]
24913fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
24914    let (text, ranges) = marked_text_ranges(marked_text, true);
24915    assert_eq!(editor.text(cx), text);
24916    assert_eq!(
24917        editor.selections.ranges(cx),
24918        ranges,
24919        "Assert selections are {}",
24920        marked_text
24921    );
24922}
24923
24924pub fn handle_signature_help_request(
24925    cx: &mut EditorLspTestContext,
24926    mocked_response: lsp::SignatureHelp,
24927) -> impl Future<Output = ()> + use<> {
24928    let mut request =
24929        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
24930            let mocked_response = mocked_response.clone();
24931            async move { Ok(Some(mocked_response)) }
24932        });
24933
24934    async move {
24935        request.next().await;
24936    }
24937}
24938
24939#[track_caller]
24940pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
24941    cx.update_editor(|editor, _, _| {
24942        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
24943            let entries = menu.entries.borrow();
24944            let entries = entries
24945                .iter()
24946                .map(|entry| entry.string.as_str())
24947                .collect::<Vec<_>>();
24948            assert_eq!(entries, expected);
24949        } else {
24950            panic!("Expected completions menu");
24951        }
24952    });
24953}
24954
24955/// Handle completion request passing a marked string specifying where the completion
24956/// should be triggered from using '|' character, what range should be replaced, and what completions
24957/// should be returned using '<' and '>' to delimit the range.
24958///
24959/// Also see `handle_completion_request_with_insert_and_replace`.
24960#[track_caller]
24961pub fn handle_completion_request(
24962    marked_string: &str,
24963    completions: Vec<&'static str>,
24964    is_incomplete: bool,
24965    counter: Arc<AtomicUsize>,
24966    cx: &mut EditorLspTestContext,
24967) -> impl Future<Output = ()> {
24968    let complete_from_marker: TextRangeMarker = '|'.into();
24969    let replace_range_marker: TextRangeMarker = ('<', '>').into();
24970    let (_, mut marked_ranges) = marked_text_ranges_by(
24971        marked_string,
24972        vec![complete_from_marker.clone(), replace_range_marker.clone()],
24973    );
24974
24975    let complete_from_position =
24976        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
24977    let replace_range =
24978        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
24979
24980    let mut request =
24981        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
24982            let completions = completions.clone();
24983            counter.fetch_add(1, atomic::Ordering::Release);
24984            async move {
24985                assert_eq!(params.text_document_position.text_document.uri, url.clone());
24986                assert_eq!(
24987                    params.text_document_position.position,
24988                    complete_from_position
24989                );
24990                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
24991                    is_incomplete,
24992                    item_defaults: None,
24993                    items: completions
24994                        .iter()
24995                        .map(|completion_text| lsp::CompletionItem {
24996                            label: completion_text.to_string(),
24997                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
24998                                range: replace_range,
24999                                new_text: completion_text.to_string(),
25000                            })),
25001                            ..Default::default()
25002                        })
25003                        .collect(),
25004                })))
25005            }
25006        });
25007
25008    async move {
25009        request.next().await;
25010    }
25011}
25012
25013/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
25014/// given instead, which also contains an `insert` range.
25015///
25016/// This function uses markers to define ranges:
25017/// - `|` marks the cursor position
25018/// - `<>` marks the replace range
25019/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
25020pub fn handle_completion_request_with_insert_and_replace(
25021    cx: &mut EditorLspTestContext,
25022    marked_string: &str,
25023    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
25024    counter: Arc<AtomicUsize>,
25025) -> impl Future<Output = ()> {
25026    let complete_from_marker: TextRangeMarker = '|'.into();
25027    let replace_range_marker: TextRangeMarker = ('<', '>').into();
25028    let insert_range_marker: TextRangeMarker = ('{', '}').into();
25029
25030    let (_, mut marked_ranges) = marked_text_ranges_by(
25031        marked_string,
25032        vec![
25033            complete_from_marker.clone(),
25034            replace_range_marker.clone(),
25035            insert_range_marker.clone(),
25036        ],
25037    );
25038
25039    let complete_from_position =
25040        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
25041    let replace_range =
25042        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
25043
25044    let insert_range = match marked_ranges.remove(&insert_range_marker) {
25045        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
25046        _ => lsp::Range {
25047            start: replace_range.start,
25048            end: complete_from_position,
25049        },
25050    };
25051
25052    let mut request =
25053        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
25054            let completions = completions.clone();
25055            counter.fetch_add(1, atomic::Ordering::Release);
25056            async move {
25057                assert_eq!(params.text_document_position.text_document.uri, url.clone());
25058                assert_eq!(
25059                    params.text_document_position.position, complete_from_position,
25060                    "marker `|` position doesn't match",
25061                );
25062                Ok(Some(lsp::CompletionResponse::Array(
25063                    completions
25064                        .iter()
25065                        .map(|(label, new_text)| lsp::CompletionItem {
25066                            label: label.to_string(),
25067                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
25068                                lsp::InsertReplaceEdit {
25069                                    insert: insert_range,
25070                                    replace: replace_range,
25071                                    new_text: new_text.to_string(),
25072                                },
25073                            )),
25074                            ..Default::default()
25075                        })
25076                        .collect(),
25077                )))
25078            }
25079        });
25080
25081    async move {
25082        request.next().await;
25083    }
25084}
25085
25086fn handle_resolve_completion_request(
25087    cx: &mut EditorLspTestContext,
25088    edits: Option<Vec<(&'static str, &'static str)>>,
25089) -> impl Future<Output = ()> {
25090    let edits = edits.map(|edits| {
25091        edits
25092            .iter()
25093            .map(|(marked_string, new_text)| {
25094                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
25095                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
25096                lsp::TextEdit::new(replace_range, new_text.to_string())
25097            })
25098            .collect::<Vec<_>>()
25099    });
25100
25101    let mut request =
25102        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
25103            let edits = edits.clone();
25104            async move {
25105                Ok(lsp::CompletionItem {
25106                    additional_text_edits: edits,
25107                    ..Default::default()
25108                })
25109            }
25110        });
25111
25112    async move {
25113        request.next().await;
25114    }
25115}
25116
25117pub(crate) fn update_test_language_settings(
25118    cx: &mut TestAppContext,
25119    f: impl Fn(&mut AllLanguageSettingsContent),
25120) {
25121    cx.update(|cx| {
25122        SettingsStore::update_global(cx, |store, cx| {
25123            store.update_user_settings::<AllLanguageSettings>(cx, f);
25124        });
25125    });
25126}
25127
25128pub(crate) fn update_test_project_settings(
25129    cx: &mut TestAppContext,
25130    f: impl Fn(&mut ProjectSettings),
25131) {
25132    cx.update(|cx| {
25133        SettingsStore::update_global(cx, |store, cx| {
25134            store.update_user_settings::<ProjectSettings>(cx, f);
25135        });
25136    });
25137}
25138
25139pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
25140    cx.update(|cx| {
25141        assets::Assets.load_test_fonts(cx);
25142        let store = SettingsStore::test(cx);
25143        cx.set_global(store);
25144        theme::init(theme::LoadThemes::JustBase, cx);
25145        release_channel::init(SemanticVersion::default(), cx);
25146        client::init_settings(cx);
25147        language::init(cx);
25148        Project::init_settings(cx);
25149        workspace::init_settings(cx);
25150        crate::init(cx);
25151    });
25152    zlog::init_test();
25153    update_test_language_settings(cx, f);
25154}
25155
25156#[track_caller]
25157fn assert_hunk_revert(
25158    not_reverted_text_with_selections: &str,
25159    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
25160    expected_reverted_text_with_selections: &str,
25161    base_text: &str,
25162    cx: &mut EditorLspTestContext,
25163) {
25164    cx.set_state(not_reverted_text_with_selections);
25165    cx.set_head_text(base_text);
25166    cx.executor().run_until_parked();
25167
25168    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
25169        let snapshot = editor.snapshot(window, cx);
25170        let reverted_hunk_statuses = snapshot
25171            .buffer_snapshot
25172            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
25173            .map(|hunk| hunk.status().kind)
25174            .collect::<Vec<_>>();
25175
25176        editor.git_restore(&Default::default(), window, cx);
25177        reverted_hunk_statuses
25178    });
25179    cx.executor().run_until_parked();
25180    cx.assert_editor_state(expected_reverted_text_with_selections);
25181    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
25182}
25183
25184#[gpui::test(iterations = 10)]
25185async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
25186    init_test(cx, |_| {});
25187
25188    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
25189    let counter = diagnostic_requests.clone();
25190
25191    let fs = FakeFs::new(cx.executor());
25192    fs.insert_tree(
25193        path!("/a"),
25194        json!({
25195            "first.rs": "fn main() { let a = 5; }",
25196            "second.rs": "// Test file",
25197        }),
25198    )
25199    .await;
25200
25201    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25202    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25203    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25204
25205    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25206    language_registry.add(rust_lang());
25207    let mut fake_servers = language_registry.register_fake_lsp(
25208        "Rust",
25209        FakeLspAdapter {
25210            capabilities: lsp::ServerCapabilities {
25211                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
25212                    lsp::DiagnosticOptions {
25213                        identifier: None,
25214                        inter_file_dependencies: true,
25215                        workspace_diagnostics: true,
25216                        work_done_progress_options: Default::default(),
25217                    },
25218                )),
25219                ..Default::default()
25220            },
25221            ..Default::default()
25222        },
25223    );
25224
25225    let editor = workspace
25226        .update(cx, |workspace, window, cx| {
25227            workspace.open_abs_path(
25228                PathBuf::from(path!("/a/first.rs")),
25229                OpenOptions::default(),
25230                window,
25231                cx,
25232            )
25233        })
25234        .unwrap()
25235        .await
25236        .unwrap()
25237        .downcast::<Editor>()
25238        .unwrap();
25239    let fake_server = fake_servers.next().await.unwrap();
25240    let server_id = fake_server.server.server_id();
25241    let mut first_request = fake_server
25242        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
25243            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
25244            let result_id = Some(new_result_id.to_string());
25245            assert_eq!(
25246                params.text_document.uri,
25247                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25248            );
25249            async move {
25250                Ok(lsp::DocumentDiagnosticReportResult::Report(
25251                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
25252                        related_documents: None,
25253                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
25254                            items: Vec::new(),
25255                            result_id,
25256                        },
25257                    }),
25258                ))
25259            }
25260        });
25261
25262    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
25263        project.update(cx, |project, cx| {
25264            let buffer_id = editor
25265                .read(cx)
25266                .buffer()
25267                .read(cx)
25268                .as_singleton()
25269                .expect("created a singleton buffer")
25270                .read(cx)
25271                .remote_id();
25272            let buffer_result_id = project
25273                .lsp_store()
25274                .read(cx)
25275                .result_id(server_id, buffer_id, cx);
25276            assert_eq!(expected, buffer_result_id);
25277        });
25278    };
25279
25280    ensure_result_id(None, cx);
25281    cx.executor().advance_clock(Duration::from_millis(60));
25282    cx.executor().run_until_parked();
25283    assert_eq!(
25284        diagnostic_requests.load(atomic::Ordering::Acquire),
25285        1,
25286        "Opening file should trigger diagnostic request"
25287    );
25288    first_request
25289        .next()
25290        .await
25291        .expect("should have sent the first diagnostics pull request");
25292    ensure_result_id(Some("1".to_string()), cx);
25293
25294    // Editing should trigger diagnostics
25295    editor.update_in(cx, |editor, window, cx| {
25296        editor.handle_input("2", window, cx)
25297    });
25298    cx.executor().advance_clock(Duration::from_millis(60));
25299    cx.executor().run_until_parked();
25300    assert_eq!(
25301        diagnostic_requests.load(atomic::Ordering::Acquire),
25302        2,
25303        "Editing should trigger diagnostic request"
25304    );
25305    ensure_result_id(Some("2".to_string()), cx);
25306
25307    // Moving cursor should not trigger diagnostic request
25308    editor.update_in(cx, |editor, window, cx| {
25309        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25310            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
25311        });
25312    });
25313    cx.executor().advance_clock(Duration::from_millis(60));
25314    cx.executor().run_until_parked();
25315    assert_eq!(
25316        diagnostic_requests.load(atomic::Ordering::Acquire),
25317        2,
25318        "Cursor movement should not trigger diagnostic request"
25319    );
25320    ensure_result_id(Some("2".to_string()), cx);
25321    // Multiple rapid edits should be debounced
25322    for _ in 0..5 {
25323        editor.update_in(cx, |editor, window, cx| {
25324            editor.handle_input("x", window, cx)
25325        });
25326    }
25327    cx.executor().advance_clock(Duration::from_millis(60));
25328    cx.executor().run_until_parked();
25329
25330    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
25331    assert!(
25332        final_requests <= 4,
25333        "Multiple rapid edits should be debounced (got {final_requests} requests)",
25334    );
25335    ensure_result_id(Some(final_requests.to_string()), cx);
25336}
25337
25338#[gpui::test]
25339async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
25340    // Regression test for issue #11671
25341    // Previously, adding a cursor after moving multiple cursors would reset
25342    // the cursor count instead of adding to the existing cursors.
25343    init_test(cx, |_| {});
25344    let mut cx = EditorTestContext::new(cx).await;
25345
25346    // Create a simple buffer with cursor at start
25347    cx.set_state(indoc! {"
25348        ˇaaaa
25349        bbbb
25350        cccc
25351        dddd
25352        eeee
25353        ffff
25354        gggg
25355        hhhh"});
25356
25357    // Add 2 cursors below (so we have 3 total)
25358    cx.update_editor(|editor, window, cx| {
25359        editor.add_selection_below(&Default::default(), window, cx);
25360        editor.add_selection_below(&Default::default(), window, cx);
25361    });
25362
25363    // Verify we have 3 cursors
25364    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
25365    assert_eq!(
25366        initial_count, 3,
25367        "Should have 3 cursors after adding 2 below"
25368    );
25369
25370    // Move down one line
25371    cx.update_editor(|editor, window, cx| {
25372        editor.move_down(&MoveDown, window, cx);
25373    });
25374
25375    // Add another cursor below
25376    cx.update_editor(|editor, window, cx| {
25377        editor.add_selection_below(&Default::default(), window, cx);
25378    });
25379
25380    // Should now have 4 cursors (3 original + 1 new)
25381    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
25382    assert_eq!(
25383        final_count, 4,
25384        "Should have 4 cursors after moving and adding another"
25385    );
25386}
25387
25388#[gpui::test(iterations = 10)]
25389async fn test_document_colors(cx: &mut TestAppContext) {
25390    let expected_color = Rgba {
25391        r: 0.33,
25392        g: 0.33,
25393        b: 0.33,
25394        a: 0.33,
25395    };
25396
25397    init_test(cx, |_| {});
25398
25399    let fs = FakeFs::new(cx.executor());
25400    fs.insert_tree(
25401        path!("/a"),
25402        json!({
25403            "first.rs": "fn main() { let a = 5; }",
25404        }),
25405    )
25406    .await;
25407
25408    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25409    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25410    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25411
25412    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25413    language_registry.add(rust_lang());
25414    let mut fake_servers = language_registry.register_fake_lsp(
25415        "Rust",
25416        FakeLspAdapter {
25417            capabilities: lsp::ServerCapabilities {
25418                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
25419                ..lsp::ServerCapabilities::default()
25420            },
25421            name: "rust-analyzer",
25422            ..FakeLspAdapter::default()
25423        },
25424    );
25425    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
25426        "Rust",
25427        FakeLspAdapter {
25428            capabilities: lsp::ServerCapabilities {
25429                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
25430                ..lsp::ServerCapabilities::default()
25431            },
25432            name: "not-rust-analyzer",
25433            ..FakeLspAdapter::default()
25434        },
25435    );
25436
25437    let editor = workspace
25438        .update(cx, |workspace, window, cx| {
25439            workspace.open_abs_path(
25440                PathBuf::from(path!("/a/first.rs")),
25441                OpenOptions::default(),
25442                window,
25443                cx,
25444            )
25445        })
25446        .unwrap()
25447        .await
25448        .unwrap()
25449        .downcast::<Editor>()
25450        .unwrap();
25451    let fake_language_server = fake_servers.next().await.unwrap();
25452    let fake_language_server_without_capabilities =
25453        fake_servers_without_capabilities.next().await.unwrap();
25454    let requests_made = Arc::new(AtomicUsize::new(0));
25455    let closure_requests_made = Arc::clone(&requests_made);
25456    let mut color_request_handle = fake_language_server
25457        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
25458            let requests_made = Arc::clone(&closure_requests_made);
25459            async move {
25460                assert_eq!(
25461                    params.text_document.uri,
25462                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25463                );
25464                requests_made.fetch_add(1, atomic::Ordering::Release);
25465                Ok(vec![
25466                    lsp::ColorInformation {
25467                        range: lsp::Range {
25468                            start: lsp::Position {
25469                                line: 0,
25470                                character: 0,
25471                            },
25472                            end: lsp::Position {
25473                                line: 0,
25474                                character: 1,
25475                            },
25476                        },
25477                        color: lsp::Color {
25478                            red: 0.33,
25479                            green: 0.33,
25480                            blue: 0.33,
25481                            alpha: 0.33,
25482                        },
25483                    },
25484                    lsp::ColorInformation {
25485                        range: lsp::Range {
25486                            start: lsp::Position {
25487                                line: 0,
25488                                character: 0,
25489                            },
25490                            end: lsp::Position {
25491                                line: 0,
25492                                character: 1,
25493                            },
25494                        },
25495                        color: lsp::Color {
25496                            red: 0.33,
25497                            green: 0.33,
25498                            blue: 0.33,
25499                            alpha: 0.33,
25500                        },
25501                    },
25502                ])
25503            }
25504        });
25505
25506    let _handle = fake_language_server_without_capabilities
25507        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
25508            panic!("Should not be called");
25509        });
25510    cx.executor().advance_clock(Duration::from_millis(100));
25511    color_request_handle.next().await.unwrap();
25512    cx.run_until_parked();
25513    assert_eq!(
25514        1,
25515        requests_made.load(atomic::Ordering::Acquire),
25516        "Should query for colors once per editor open"
25517    );
25518    editor.update_in(cx, |editor, _, cx| {
25519        assert_eq!(
25520            vec![expected_color],
25521            extract_color_inlays(editor, cx),
25522            "Should have an initial inlay"
25523        );
25524    });
25525
25526    // opening another file in a split should not influence the LSP query counter
25527    workspace
25528        .update(cx, |workspace, window, cx| {
25529            assert_eq!(
25530                workspace.panes().len(),
25531                1,
25532                "Should have one pane with one editor"
25533            );
25534            workspace.move_item_to_pane_in_direction(
25535                &MoveItemToPaneInDirection {
25536                    direction: SplitDirection::Right,
25537                    focus: false,
25538                    clone: true,
25539                },
25540                window,
25541                cx,
25542            );
25543        })
25544        .unwrap();
25545    cx.run_until_parked();
25546    workspace
25547        .update(cx, |workspace, _, cx| {
25548            let panes = workspace.panes();
25549            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
25550            for pane in panes {
25551                let editor = pane
25552                    .read(cx)
25553                    .active_item()
25554                    .and_then(|item| item.downcast::<Editor>())
25555                    .expect("Should have opened an editor in each split");
25556                let editor_file = editor
25557                    .read(cx)
25558                    .buffer()
25559                    .read(cx)
25560                    .as_singleton()
25561                    .expect("test deals with singleton buffers")
25562                    .read(cx)
25563                    .file()
25564                    .expect("test buffese should have a file")
25565                    .path();
25566                assert_eq!(
25567                    editor_file.as_ref(),
25568                    Path::new("first.rs"),
25569                    "Both editors should be opened for the same file"
25570                )
25571            }
25572        })
25573        .unwrap();
25574
25575    cx.executor().advance_clock(Duration::from_millis(500));
25576    let save = editor.update_in(cx, |editor, window, cx| {
25577        editor.move_to_end(&MoveToEnd, window, cx);
25578        editor.handle_input("dirty", window, cx);
25579        editor.save(
25580            SaveOptions {
25581                format: true,
25582                autosave: true,
25583            },
25584            project.clone(),
25585            window,
25586            cx,
25587        )
25588    });
25589    save.await.unwrap();
25590
25591    color_request_handle.next().await.unwrap();
25592    cx.run_until_parked();
25593    assert_eq!(
25594        3,
25595        requests_made.load(atomic::Ordering::Acquire),
25596        "Should query for colors once per save and once per formatting after save"
25597    );
25598
25599    drop(editor);
25600    let close = workspace
25601        .update(cx, |workspace, window, cx| {
25602            workspace.active_pane().update(cx, |pane, cx| {
25603                pane.close_active_item(&CloseActiveItem::default(), window, cx)
25604            })
25605        })
25606        .unwrap();
25607    close.await.unwrap();
25608    let close = workspace
25609        .update(cx, |workspace, window, cx| {
25610            workspace.active_pane().update(cx, |pane, cx| {
25611                pane.close_active_item(&CloseActiveItem::default(), window, cx)
25612            })
25613        })
25614        .unwrap();
25615    close.await.unwrap();
25616    assert_eq!(
25617        3,
25618        requests_made.load(atomic::Ordering::Acquire),
25619        "After saving and closing all editors, no extra requests should be made"
25620    );
25621    workspace
25622        .update(cx, |workspace, _, cx| {
25623            assert!(
25624                workspace.active_item(cx).is_none(),
25625                "Should close all editors"
25626            )
25627        })
25628        .unwrap();
25629
25630    workspace
25631        .update(cx, |workspace, window, cx| {
25632            workspace.active_pane().update(cx, |pane, cx| {
25633                pane.navigate_backward(&workspace::GoBack, window, cx);
25634            })
25635        })
25636        .unwrap();
25637    cx.executor().advance_clock(Duration::from_millis(100));
25638    cx.run_until_parked();
25639    let editor = workspace
25640        .update(cx, |workspace, _, cx| {
25641            workspace
25642                .active_item(cx)
25643                .expect("Should have reopened the editor again after navigating back")
25644                .downcast::<Editor>()
25645                .expect("Should be an editor")
25646        })
25647        .unwrap();
25648    color_request_handle.next().await.unwrap();
25649    assert_eq!(
25650        3,
25651        requests_made.load(atomic::Ordering::Acquire),
25652        "Cache should be reused on buffer close and reopen"
25653    );
25654    editor.update(cx, |editor, cx| {
25655        assert_eq!(
25656            vec![expected_color],
25657            extract_color_inlays(editor, cx),
25658            "Should have an initial inlay"
25659        );
25660    });
25661
25662    drop(color_request_handle);
25663    let closure_requests_made = Arc::clone(&requests_made);
25664    let mut empty_color_request_handle = fake_language_server
25665        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
25666            let requests_made = Arc::clone(&closure_requests_made);
25667            async move {
25668                assert_eq!(
25669                    params.text_document.uri,
25670                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25671                );
25672                requests_made.fetch_add(1, atomic::Ordering::Release);
25673                Ok(Vec::new())
25674            }
25675        });
25676    let save = editor.update_in(cx, |editor, window, cx| {
25677        editor.move_to_end(&MoveToEnd, window, cx);
25678        editor.handle_input("dirty_again", window, cx);
25679        editor.save(
25680            SaveOptions {
25681                format: false,
25682                autosave: true,
25683            },
25684            project.clone(),
25685            window,
25686            cx,
25687        )
25688    });
25689    save.await.unwrap();
25690
25691    empty_color_request_handle.next().await.unwrap();
25692    cx.run_until_parked();
25693    assert_eq!(
25694        4,
25695        requests_made.load(atomic::Ordering::Acquire),
25696        "Should query for colors once per save only, as formatting was not requested"
25697    );
25698    editor.update(cx, |editor, cx| {
25699        assert_eq!(
25700            Vec::<Rgba>::new(),
25701            extract_color_inlays(editor, cx),
25702            "Should clear all colors when the server returns an empty response"
25703        );
25704    });
25705}
25706
25707#[gpui::test]
25708async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
25709    init_test(cx, |_| {});
25710    let (editor, cx) = cx.add_window_view(Editor::single_line);
25711    editor.update_in(cx, |editor, window, cx| {
25712        editor.set_text("oops\n\nwow\n", window, cx)
25713    });
25714    cx.run_until_parked();
25715    editor.update(cx, |editor, cx| {
25716        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
25717    });
25718    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
25719    cx.run_until_parked();
25720    editor.update(cx, |editor, cx| {
25721        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
25722    });
25723}
25724
25725#[gpui::test]
25726async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
25727    init_test(cx, |_| {});
25728
25729    cx.update(|cx| {
25730        register_project_item::<Editor>(cx);
25731    });
25732
25733    let fs = FakeFs::new(cx.executor());
25734    fs.insert_tree("/root1", json!({})).await;
25735    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
25736        .await;
25737
25738    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
25739    let (workspace, cx) =
25740        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
25741
25742    let worktree_id = project.update(cx, |project, cx| {
25743        project.worktrees(cx).next().unwrap().read(cx).id()
25744    });
25745
25746    let handle = workspace
25747        .update_in(cx, |workspace, window, cx| {
25748            let project_path = (worktree_id, "one.pdf");
25749            workspace.open_path(project_path, None, true, window, cx)
25750        })
25751        .await
25752        .unwrap();
25753
25754    assert_eq!(
25755        handle.to_any().entity_type(),
25756        TypeId::of::<InvalidBufferView>()
25757    );
25758}
25759
25760#[gpui::test]
25761async fn test_select_next_prev_syntax_node(cx: &mut TestAppContext) {
25762    init_test(cx, |_| {});
25763
25764    let language = Arc::new(Language::new(
25765        LanguageConfig::default(),
25766        Some(tree_sitter_rust::LANGUAGE.into()),
25767    ));
25768
25769    // Test hierarchical sibling navigation
25770    let text = r#"
25771        fn outer() {
25772            if condition {
25773                let a = 1;
25774            }
25775            let b = 2;
25776        }
25777
25778        fn another() {
25779            let c = 3;
25780        }
25781    "#;
25782
25783    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
25784    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
25785    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
25786
25787    // Wait for parsing to complete
25788    editor
25789        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
25790        .await;
25791
25792    editor.update_in(cx, |editor, window, cx| {
25793        // Start by selecting "let a = 1;" inside the if block
25794        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25795            s.select_display_ranges([
25796                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 26)
25797            ]);
25798        });
25799
25800        let initial_selection = editor.selections.display_ranges(cx);
25801        assert_eq!(initial_selection.len(), 1, "Should have one selection");
25802
25803        // Test select next sibling - should move up levels to find the next sibling
25804        // Since "let a = 1;" has no siblings in the if block, it should move up
25805        // to find "let b = 2;" which is a sibling of the if block
25806        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
25807        let next_selection = editor.selections.display_ranges(cx);
25808
25809        // Should have a selection and it should be different from the initial
25810        assert_eq!(
25811            next_selection.len(),
25812            1,
25813            "Should have one selection after next"
25814        );
25815        assert_ne!(
25816            next_selection[0], initial_selection[0],
25817            "Next sibling selection should be different"
25818        );
25819
25820        // Test hierarchical navigation by going to the end of the current function
25821        // and trying to navigate to the next function
25822        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25823            s.select_display_ranges([
25824                DisplayPoint::new(DisplayRow(5), 12)..DisplayPoint::new(DisplayRow(5), 22)
25825            ]);
25826        });
25827
25828        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
25829        let function_next_selection = editor.selections.display_ranges(cx);
25830
25831        // Should move to the next function
25832        assert_eq!(
25833            function_next_selection.len(),
25834            1,
25835            "Should have one selection after function next"
25836        );
25837
25838        // Test select previous sibling navigation
25839        editor.select_prev_syntax_node(&SelectPreviousSyntaxNode, window, cx);
25840        let prev_selection = editor.selections.display_ranges(cx);
25841
25842        // Should have a selection and it should be different
25843        assert_eq!(
25844            prev_selection.len(),
25845            1,
25846            "Should have one selection after prev"
25847        );
25848        assert_ne!(
25849            prev_selection[0], function_next_selection[0],
25850            "Previous sibling selection should be different from next"
25851        );
25852    });
25853}
25854
25855#[gpui::test]
25856async fn test_next_prev_document_highlight(cx: &mut TestAppContext) {
25857    init_test(cx, |_| {});
25858
25859    let mut cx = EditorTestContext::new(cx).await;
25860    cx.set_state(
25861        "let ˇvariable = 42;
25862let another = variable + 1;
25863let result = variable * 2;",
25864    );
25865
25866    // Set up document highlights manually (simulating LSP response)
25867    cx.update_editor(|editor, _window, cx| {
25868        let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
25869
25870        // Create highlights for "variable" occurrences
25871        let highlight_ranges = [
25872            Point::new(0, 4)..Point::new(0, 12),  // First "variable"
25873            Point::new(1, 14)..Point::new(1, 22), // Second "variable"
25874            Point::new(2, 13)..Point::new(2, 21), // Third "variable"
25875        ];
25876
25877        let anchor_ranges: Vec<_> = highlight_ranges
25878            .iter()
25879            .map(|range| range.clone().to_anchors(&buffer_snapshot))
25880            .collect();
25881
25882        editor.highlight_background::<DocumentHighlightRead>(
25883            &anchor_ranges,
25884            |theme| theme.colors().editor_document_highlight_read_background,
25885            cx,
25886        );
25887    });
25888
25889    // Go to next highlight - should move to second "variable"
25890    cx.update_editor(|editor, window, cx| {
25891        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
25892    });
25893    cx.assert_editor_state(
25894        "let variable = 42;
25895let another = ˇvariable + 1;
25896let result = variable * 2;",
25897    );
25898
25899    // Go to next highlight - should move to third "variable"
25900    cx.update_editor(|editor, window, cx| {
25901        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
25902    });
25903    cx.assert_editor_state(
25904        "let variable = 42;
25905let another = variable + 1;
25906let result = ˇvariable * 2;",
25907    );
25908
25909    // Go to next highlight - should stay at third "variable" (no wrap-around)
25910    cx.update_editor(|editor, window, cx| {
25911        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
25912    });
25913    cx.assert_editor_state(
25914        "let variable = 42;
25915let another = variable + 1;
25916let result = ˇvariable * 2;",
25917    );
25918
25919    // Now test going backwards from third position
25920    cx.update_editor(|editor, window, cx| {
25921        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
25922    });
25923    cx.assert_editor_state(
25924        "let variable = 42;
25925let another = ˇvariable + 1;
25926let result = variable * 2;",
25927    );
25928
25929    // Go to previous highlight - should move to first "variable"
25930    cx.update_editor(|editor, window, cx| {
25931        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
25932    });
25933    cx.assert_editor_state(
25934        "let ˇvariable = 42;
25935let another = variable + 1;
25936let result = variable * 2;",
25937    );
25938
25939    // Go to previous highlight - should stay on first "variable"
25940    cx.update_editor(|editor, window, cx| {
25941        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
25942    });
25943    cx.assert_editor_state(
25944        "let ˇvariable = 42;
25945let another = variable + 1;
25946let result = variable * 2;",
25947    );
25948}
25949
25950#[track_caller]
25951fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
25952    editor
25953        .all_inlays(cx)
25954        .into_iter()
25955        .filter_map(|inlay| inlay.get_color())
25956        .map(Rgba::from)
25957        .collect()
25958}