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    cx.set_state(indoc! { r#"fn a() {
 9152          // what
 9153          // a
 9154          // ˇlong
 9155          // method
 9156          // I
 9157          // sure
 9158          // hope
 9159          // it
 9160          // works
 9161    }"# });
 9162
 9163    let buffer = cx.update_multibuffer(|multibuffer, _| multibuffer.as_singleton().unwrap());
 9164    let multi_buffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
 9165    cx.update(|_, cx| {
 9166        multi_buffer.update(cx, |multi_buffer, cx| {
 9167            multi_buffer.set_excerpts_for_path(
 9168                PathKey::for_buffer(&buffer, cx),
 9169                buffer,
 9170                [Point::new(1, 0)..Point::new(1, 0)],
 9171                3,
 9172                cx,
 9173            );
 9174        });
 9175    });
 9176
 9177    let editor2 = cx.new_window_entity(|window, cx| {
 9178        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
 9179    });
 9180
 9181    let mut cx = EditorTestContext::for_editor_in(editor2, &mut cx).await;
 9182    cx.update_editor(|editor, window, cx| {
 9183        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 9184            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)]);
 9185        })
 9186    });
 9187
 9188    cx.assert_editor_state(indoc! { "
 9189        fn a() {
 9190              // what
 9191              // a
 9192        ˇ      // long
 9193              // method"});
 9194
 9195    cx.update_editor(|editor, window, cx| {
 9196        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9197    });
 9198
 9199    // Although we could potentially make the action work when the syntax node
 9200    // is half-hidden, it seems a bit dangerous as you can't easily tell what it
 9201    // did. Maybe we could also expand the excerpt to contain the range?
 9202    cx.assert_editor_state(indoc! { "
 9203        fn a() {
 9204              // what
 9205              // a
 9206        ˇ      // long
 9207              // method"});
 9208}
 9209
 9210#[gpui::test]
 9211async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 9212    init_test(cx, |_| {});
 9213
 9214    let base_text = r#"
 9215        impl A {
 9216            // this is an uncommitted comment
 9217
 9218            fn b() {
 9219                c();
 9220            }
 9221
 9222            // this is another uncommitted comment
 9223
 9224            fn d() {
 9225                // e
 9226                // f
 9227            }
 9228        }
 9229
 9230        fn g() {
 9231            // h
 9232        }
 9233    "#
 9234    .unindent();
 9235
 9236    let text = r#"
 9237        ˇimpl A {
 9238
 9239            fn b() {
 9240                c();
 9241            }
 9242
 9243            fn d() {
 9244                // e
 9245                // f
 9246            }
 9247        }
 9248
 9249        fn g() {
 9250            // h
 9251        }
 9252    "#
 9253    .unindent();
 9254
 9255    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9256    cx.set_state(&text);
 9257    cx.set_head_text(&base_text);
 9258    cx.update_editor(|editor, window, cx| {
 9259        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 9260    });
 9261
 9262    cx.assert_state_with_diff(
 9263        "
 9264        ˇimpl A {
 9265      -     // this is an uncommitted comment
 9266
 9267            fn b() {
 9268                c();
 9269            }
 9270
 9271      -     // this is another uncommitted comment
 9272      -
 9273            fn d() {
 9274                // e
 9275                // f
 9276            }
 9277        }
 9278
 9279        fn g() {
 9280            // h
 9281        }
 9282    "
 9283        .unindent(),
 9284    );
 9285
 9286    let expected_display_text = "
 9287        impl A {
 9288            // this is an uncommitted comment
 9289
 9290            fn b() {
 9291 9292            }
 9293
 9294            // this is another uncommitted comment
 9295
 9296            fn d() {
 9297 9298            }
 9299        }
 9300
 9301        fn g() {
 9302 9303        }
 9304        "
 9305    .unindent();
 9306
 9307    cx.update_editor(|editor, window, cx| {
 9308        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 9309        assert_eq!(editor.display_text(cx), expected_display_text);
 9310    });
 9311}
 9312
 9313#[gpui::test]
 9314async fn test_autoindent(cx: &mut TestAppContext) {
 9315    init_test(cx, |_| {});
 9316
 9317    let language = Arc::new(
 9318        Language::new(
 9319            LanguageConfig {
 9320                brackets: BracketPairConfig {
 9321                    pairs: vec![
 9322                        BracketPair {
 9323                            start: "{".to_string(),
 9324                            end: "}".to_string(),
 9325                            close: false,
 9326                            surround: false,
 9327                            newline: true,
 9328                        },
 9329                        BracketPair {
 9330                            start: "(".to_string(),
 9331                            end: ")".to_string(),
 9332                            close: false,
 9333                            surround: false,
 9334                            newline: true,
 9335                        },
 9336                    ],
 9337                    ..Default::default()
 9338                },
 9339                ..Default::default()
 9340            },
 9341            Some(tree_sitter_rust::LANGUAGE.into()),
 9342        )
 9343        .with_indents_query(
 9344            r#"
 9345                (_ "(" ")" @end) @indent
 9346                (_ "{" "}" @end) @indent
 9347            "#,
 9348        )
 9349        .unwrap(),
 9350    );
 9351
 9352    let text = "fn a() {}";
 9353
 9354    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9355    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9356    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9357    editor
 9358        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9359        .await;
 9360
 9361    editor.update_in(cx, |editor, window, cx| {
 9362        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9363            s.select_ranges([5..5, 8..8, 9..9])
 9364        });
 9365        editor.newline(&Newline, window, cx);
 9366        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 9367        assert_eq!(
 9368            editor.selections.ranges(cx),
 9369            &[
 9370                Point::new(1, 4)..Point::new(1, 4),
 9371                Point::new(3, 4)..Point::new(3, 4),
 9372                Point::new(5, 0)..Point::new(5, 0)
 9373            ]
 9374        );
 9375    });
 9376}
 9377
 9378#[gpui::test]
 9379async fn test_autoindent_disabled(cx: &mut TestAppContext) {
 9380    init_test(cx, |settings| settings.defaults.auto_indent = Some(false));
 9381
 9382    let language = Arc::new(
 9383        Language::new(
 9384            LanguageConfig {
 9385                brackets: BracketPairConfig {
 9386                    pairs: vec![
 9387                        BracketPair {
 9388                            start: "{".to_string(),
 9389                            end: "}".to_string(),
 9390                            close: false,
 9391                            surround: false,
 9392                            newline: true,
 9393                        },
 9394                        BracketPair {
 9395                            start: "(".to_string(),
 9396                            end: ")".to_string(),
 9397                            close: false,
 9398                            surround: false,
 9399                            newline: true,
 9400                        },
 9401                    ],
 9402                    ..Default::default()
 9403                },
 9404                ..Default::default()
 9405            },
 9406            Some(tree_sitter_rust::LANGUAGE.into()),
 9407        )
 9408        .with_indents_query(
 9409            r#"
 9410                (_ "(" ")" @end) @indent
 9411                (_ "{" "}" @end) @indent
 9412            "#,
 9413        )
 9414        .unwrap(),
 9415    );
 9416
 9417    let text = "fn a() {}";
 9418
 9419    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9420    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9421    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9422    editor
 9423        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9424        .await;
 9425
 9426    editor.update_in(cx, |editor, window, cx| {
 9427        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9428            s.select_ranges([5..5, 8..8, 9..9])
 9429        });
 9430        editor.newline(&Newline, window, cx);
 9431        assert_eq!(
 9432            editor.text(cx),
 9433            indoc!(
 9434                "
 9435                fn a(
 9436
 9437                ) {
 9438
 9439                }
 9440                "
 9441            )
 9442        );
 9443        assert_eq!(
 9444            editor.selections.ranges(cx),
 9445            &[
 9446                Point::new(1, 0)..Point::new(1, 0),
 9447                Point::new(3, 0)..Point::new(3, 0),
 9448                Point::new(5, 0)..Point::new(5, 0)
 9449            ]
 9450        );
 9451    });
 9452}
 9453
 9454#[gpui::test]
 9455async fn test_autoindent_disabled_with_nested_language(cx: &mut TestAppContext) {
 9456    init_test(cx, |settings| {
 9457        settings.defaults.auto_indent = Some(true);
 9458        settings.languages.0.insert(
 9459            "python".into(),
 9460            LanguageSettingsContent {
 9461                auto_indent: Some(false),
 9462                ..Default::default()
 9463            },
 9464        );
 9465    });
 9466
 9467    let mut cx = EditorTestContext::new(cx).await;
 9468
 9469    let injected_language = Arc::new(
 9470        Language::new(
 9471            LanguageConfig {
 9472                brackets: BracketPairConfig {
 9473                    pairs: vec![
 9474                        BracketPair {
 9475                            start: "{".to_string(),
 9476                            end: "}".to_string(),
 9477                            close: false,
 9478                            surround: false,
 9479                            newline: true,
 9480                        },
 9481                        BracketPair {
 9482                            start: "(".to_string(),
 9483                            end: ")".to_string(),
 9484                            close: true,
 9485                            surround: false,
 9486                            newline: true,
 9487                        },
 9488                    ],
 9489                    ..Default::default()
 9490                },
 9491                name: "python".into(),
 9492                ..Default::default()
 9493            },
 9494            Some(tree_sitter_python::LANGUAGE.into()),
 9495        )
 9496        .with_indents_query(
 9497            r#"
 9498                (_ "(" ")" @end) @indent
 9499                (_ "{" "}" @end) @indent
 9500            "#,
 9501        )
 9502        .unwrap(),
 9503    );
 9504
 9505    let language = Arc::new(
 9506        Language::new(
 9507            LanguageConfig {
 9508                brackets: BracketPairConfig {
 9509                    pairs: vec![
 9510                        BracketPair {
 9511                            start: "{".to_string(),
 9512                            end: "}".to_string(),
 9513                            close: false,
 9514                            surround: false,
 9515                            newline: true,
 9516                        },
 9517                        BracketPair {
 9518                            start: "(".to_string(),
 9519                            end: ")".to_string(),
 9520                            close: true,
 9521                            surround: false,
 9522                            newline: true,
 9523                        },
 9524                    ],
 9525                    ..Default::default()
 9526                },
 9527                name: LanguageName::new("rust"),
 9528                ..Default::default()
 9529            },
 9530            Some(tree_sitter_rust::LANGUAGE.into()),
 9531        )
 9532        .with_indents_query(
 9533            r#"
 9534                (_ "(" ")" @end) @indent
 9535                (_ "{" "}" @end) @indent
 9536            "#,
 9537        )
 9538        .unwrap()
 9539        .with_injection_query(
 9540            r#"
 9541            (macro_invocation
 9542                macro: (identifier) @_macro_name
 9543                (token_tree) @injection.content
 9544                (#set! injection.language "python"))
 9545           "#,
 9546        )
 9547        .unwrap(),
 9548    );
 9549
 9550    cx.language_registry().add(injected_language);
 9551    cx.language_registry().add(language.clone());
 9552
 9553    cx.update_buffer(|buffer, cx| {
 9554        buffer.set_language(Some(language), cx);
 9555    });
 9556
 9557    cx.set_state(r#"struct A {ˇ}"#);
 9558
 9559    cx.update_editor(|editor, window, cx| {
 9560        editor.newline(&Default::default(), window, cx);
 9561    });
 9562
 9563    cx.assert_editor_state(indoc!(
 9564        "struct A {
 9565            ˇ
 9566        }"
 9567    ));
 9568
 9569    cx.set_state(r#"select_biased!(ˇ)"#);
 9570
 9571    cx.update_editor(|editor, window, cx| {
 9572        editor.newline(&Default::default(), window, cx);
 9573        editor.handle_input("def ", window, cx);
 9574        editor.handle_input("(", window, cx);
 9575        editor.newline(&Default::default(), window, cx);
 9576        editor.handle_input("a", window, cx);
 9577    });
 9578
 9579    cx.assert_editor_state(indoc!(
 9580        "select_biased!(
 9581        def (
 9582 9583        )
 9584        )"
 9585    ));
 9586}
 9587
 9588#[gpui::test]
 9589async fn test_autoindent_selections(cx: &mut TestAppContext) {
 9590    init_test(cx, |_| {});
 9591
 9592    {
 9593        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9594        cx.set_state(indoc! {"
 9595            impl A {
 9596
 9597                fn b() {}
 9598
 9599            «fn c() {
 9600
 9601            }ˇ»
 9602            }
 9603        "});
 9604
 9605        cx.update_editor(|editor, window, cx| {
 9606            editor.autoindent(&Default::default(), window, cx);
 9607        });
 9608
 9609        cx.assert_editor_state(indoc! {"
 9610            impl A {
 9611
 9612                fn b() {}
 9613
 9614                «fn c() {
 9615
 9616                }ˇ»
 9617            }
 9618        "});
 9619    }
 9620
 9621    {
 9622        let mut cx = EditorTestContext::new_multibuffer(
 9623            cx,
 9624            [indoc! { "
 9625                impl A {
 9626                «
 9627                // a
 9628                fn b(){}
 9629                »
 9630                «
 9631                    }
 9632                    fn c(){}
 9633                »
 9634            "}],
 9635        );
 9636
 9637        let buffer = cx.update_editor(|editor, _, cx| {
 9638            let buffer = editor.buffer().update(cx, |buffer, _| {
 9639                buffer.all_buffers().iter().next().unwrap().clone()
 9640            });
 9641            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 9642            buffer
 9643        });
 9644
 9645        cx.run_until_parked();
 9646        cx.update_editor(|editor, window, cx| {
 9647            editor.select_all(&Default::default(), window, cx);
 9648            editor.autoindent(&Default::default(), window, cx)
 9649        });
 9650        cx.run_until_parked();
 9651
 9652        cx.update(|_, cx| {
 9653            assert_eq!(
 9654                buffer.read(cx).text(),
 9655                indoc! { "
 9656                    impl A {
 9657
 9658                        // a
 9659                        fn b(){}
 9660
 9661
 9662                    }
 9663                    fn c(){}
 9664
 9665                " }
 9666            )
 9667        });
 9668    }
 9669}
 9670
 9671#[gpui::test]
 9672async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 9673    init_test(cx, |_| {});
 9674
 9675    let mut cx = EditorTestContext::new(cx).await;
 9676
 9677    let language = Arc::new(Language::new(
 9678        LanguageConfig {
 9679            brackets: BracketPairConfig {
 9680                pairs: vec![
 9681                    BracketPair {
 9682                        start: "{".to_string(),
 9683                        end: "}".to_string(),
 9684                        close: true,
 9685                        surround: true,
 9686                        newline: true,
 9687                    },
 9688                    BracketPair {
 9689                        start: "(".to_string(),
 9690                        end: ")".to_string(),
 9691                        close: true,
 9692                        surround: true,
 9693                        newline: true,
 9694                    },
 9695                    BracketPair {
 9696                        start: "/*".to_string(),
 9697                        end: " */".to_string(),
 9698                        close: true,
 9699                        surround: true,
 9700                        newline: true,
 9701                    },
 9702                    BracketPair {
 9703                        start: "[".to_string(),
 9704                        end: "]".to_string(),
 9705                        close: false,
 9706                        surround: false,
 9707                        newline: true,
 9708                    },
 9709                    BracketPair {
 9710                        start: "\"".to_string(),
 9711                        end: "\"".to_string(),
 9712                        close: true,
 9713                        surround: true,
 9714                        newline: false,
 9715                    },
 9716                    BracketPair {
 9717                        start: "<".to_string(),
 9718                        end: ">".to_string(),
 9719                        close: false,
 9720                        surround: true,
 9721                        newline: true,
 9722                    },
 9723                ],
 9724                ..Default::default()
 9725            },
 9726            autoclose_before: "})]".to_string(),
 9727            ..Default::default()
 9728        },
 9729        Some(tree_sitter_rust::LANGUAGE.into()),
 9730    ));
 9731
 9732    cx.language_registry().add(language.clone());
 9733    cx.update_buffer(|buffer, cx| {
 9734        buffer.set_language(Some(language), cx);
 9735    });
 9736
 9737    cx.set_state(
 9738        &r#"
 9739            🏀ˇ
 9740            εˇ
 9741            ❤️ˇ
 9742        "#
 9743        .unindent(),
 9744    );
 9745
 9746    // autoclose multiple nested brackets at multiple cursors
 9747    cx.update_editor(|editor, window, cx| {
 9748        editor.handle_input("{", window, cx);
 9749        editor.handle_input("{", window, cx);
 9750        editor.handle_input("{", window, cx);
 9751    });
 9752    cx.assert_editor_state(
 9753        &"
 9754            🏀{{{ˇ}}}
 9755            ε{{{ˇ}}}
 9756            ❤️{{{ˇ}}}
 9757        "
 9758        .unindent(),
 9759    );
 9760
 9761    // insert a different closing bracket
 9762    cx.update_editor(|editor, window, cx| {
 9763        editor.handle_input(")", window, cx);
 9764    });
 9765    cx.assert_editor_state(
 9766        &"
 9767            🏀{{{)ˇ}}}
 9768            ε{{{)ˇ}}}
 9769            ❤️{{{)ˇ}}}
 9770        "
 9771        .unindent(),
 9772    );
 9773
 9774    // skip over the auto-closed brackets when typing a closing bracket
 9775    cx.update_editor(|editor, window, cx| {
 9776        editor.move_right(&MoveRight, window, cx);
 9777        editor.handle_input("}", window, cx);
 9778        editor.handle_input("}", window, cx);
 9779        editor.handle_input("}", window, cx);
 9780    });
 9781    cx.assert_editor_state(
 9782        &"
 9783            🏀{{{)}}}}ˇ
 9784            ε{{{)}}}}ˇ
 9785            ❤️{{{)}}}}ˇ
 9786        "
 9787        .unindent(),
 9788    );
 9789
 9790    // autoclose multi-character pairs
 9791    cx.set_state(
 9792        &"
 9793            ˇ
 9794            ˇ
 9795        "
 9796        .unindent(),
 9797    );
 9798    cx.update_editor(|editor, window, cx| {
 9799        editor.handle_input("/", window, cx);
 9800        editor.handle_input("*", window, cx);
 9801    });
 9802    cx.assert_editor_state(
 9803        &"
 9804            /*ˇ */
 9805            /*ˇ */
 9806        "
 9807        .unindent(),
 9808    );
 9809
 9810    // one cursor autocloses a multi-character pair, one cursor
 9811    // does not autoclose.
 9812    cx.set_state(
 9813        &"
 9814 9815            ˇ
 9816        "
 9817        .unindent(),
 9818    );
 9819    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
 9820    cx.assert_editor_state(
 9821        &"
 9822            /*ˇ */
 9823 9824        "
 9825        .unindent(),
 9826    );
 9827
 9828    // Don't autoclose if the next character isn't whitespace and isn't
 9829    // listed in the language's "autoclose_before" section.
 9830    cx.set_state("ˇa b");
 9831    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 9832    cx.assert_editor_state("{ˇa b");
 9833
 9834    // Don't autoclose if `close` is false for the bracket pair
 9835    cx.set_state("ˇ");
 9836    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
 9837    cx.assert_editor_state("");
 9838
 9839    // Surround with brackets if text is selected
 9840    cx.set_state("«aˇ» b");
 9841    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 9842    cx.assert_editor_state("{«aˇ»} b");
 9843
 9844    // Autoclose when not immediately after a word character
 9845    cx.set_state("a ˇ");
 9846    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9847    cx.assert_editor_state("a \"ˇ\"");
 9848
 9849    // Autoclose pair where the start and end characters are the same
 9850    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9851    cx.assert_editor_state("a \"\"ˇ");
 9852
 9853    // Don't autoclose when immediately after a word character
 9854    cx.set_state("");
 9855    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9856    cx.assert_editor_state("a\"ˇ");
 9857
 9858    // Do autoclose when after a non-word character
 9859    cx.set_state("");
 9860    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9861    cx.assert_editor_state("{\"ˇ\"");
 9862
 9863    // Non identical pairs autoclose regardless of preceding character
 9864    cx.set_state("");
 9865    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 9866    cx.assert_editor_state("a{ˇ}");
 9867
 9868    // Don't autoclose pair if autoclose is disabled
 9869    cx.set_state("ˇ");
 9870    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 9871    cx.assert_editor_state("");
 9872
 9873    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
 9874    cx.set_state("«aˇ» b");
 9875    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 9876    cx.assert_editor_state("<«aˇ»> b");
 9877}
 9878
 9879#[gpui::test]
 9880async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
 9881    init_test(cx, |settings| {
 9882        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 9883    });
 9884
 9885    let mut cx = EditorTestContext::new(cx).await;
 9886
 9887    let language = Arc::new(Language::new(
 9888        LanguageConfig {
 9889            brackets: BracketPairConfig {
 9890                pairs: vec![
 9891                    BracketPair {
 9892                        start: "{".to_string(),
 9893                        end: "}".to_string(),
 9894                        close: true,
 9895                        surround: true,
 9896                        newline: true,
 9897                    },
 9898                    BracketPair {
 9899                        start: "(".to_string(),
 9900                        end: ")".to_string(),
 9901                        close: true,
 9902                        surround: true,
 9903                        newline: true,
 9904                    },
 9905                    BracketPair {
 9906                        start: "[".to_string(),
 9907                        end: "]".to_string(),
 9908                        close: false,
 9909                        surround: false,
 9910                        newline: true,
 9911                    },
 9912                ],
 9913                ..Default::default()
 9914            },
 9915            autoclose_before: "})]".to_string(),
 9916            ..Default::default()
 9917        },
 9918        Some(tree_sitter_rust::LANGUAGE.into()),
 9919    ));
 9920
 9921    cx.language_registry().add(language.clone());
 9922    cx.update_buffer(|buffer, cx| {
 9923        buffer.set_language(Some(language), cx);
 9924    });
 9925
 9926    cx.set_state(
 9927        &"
 9928            ˇ
 9929            ˇ
 9930            ˇ
 9931        "
 9932        .unindent(),
 9933    );
 9934
 9935    // ensure only matching closing brackets are skipped over
 9936    cx.update_editor(|editor, window, cx| {
 9937        editor.handle_input("}", window, cx);
 9938        editor.move_left(&MoveLeft, window, cx);
 9939        editor.handle_input(")", window, cx);
 9940        editor.move_left(&MoveLeft, window, cx);
 9941    });
 9942    cx.assert_editor_state(
 9943        &"
 9944            ˇ)}
 9945            ˇ)}
 9946            ˇ)}
 9947        "
 9948        .unindent(),
 9949    );
 9950
 9951    // skip-over closing brackets at multiple cursors
 9952    cx.update_editor(|editor, window, cx| {
 9953        editor.handle_input(")", window, cx);
 9954        editor.handle_input("}", window, cx);
 9955    });
 9956    cx.assert_editor_state(
 9957        &"
 9958            )}ˇ
 9959            )}ˇ
 9960            )}ˇ
 9961        "
 9962        .unindent(),
 9963    );
 9964
 9965    // ignore non-close brackets
 9966    cx.update_editor(|editor, window, cx| {
 9967        editor.handle_input("]", window, cx);
 9968        editor.move_left(&MoveLeft, window, cx);
 9969        editor.handle_input("]", window, cx);
 9970    });
 9971    cx.assert_editor_state(
 9972        &"
 9973            )}]ˇ]
 9974            )}]ˇ]
 9975            )}]ˇ]
 9976        "
 9977        .unindent(),
 9978    );
 9979}
 9980
 9981#[gpui::test]
 9982async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
 9983    init_test(cx, |_| {});
 9984
 9985    let mut cx = EditorTestContext::new(cx).await;
 9986
 9987    let html_language = Arc::new(
 9988        Language::new(
 9989            LanguageConfig {
 9990                name: "HTML".into(),
 9991                brackets: BracketPairConfig {
 9992                    pairs: vec![
 9993                        BracketPair {
 9994                            start: "<".into(),
 9995                            end: ">".into(),
 9996                            close: true,
 9997                            ..Default::default()
 9998                        },
 9999                        BracketPair {
10000                            start: "{".into(),
10001                            end: "}".into(),
10002                            close: true,
10003                            ..Default::default()
10004                        },
10005                        BracketPair {
10006                            start: "(".into(),
10007                            end: ")".into(),
10008                            close: true,
10009                            ..Default::default()
10010                        },
10011                    ],
10012                    ..Default::default()
10013                },
10014                autoclose_before: "})]>".into(),
10015                ..Default::default()
10016            },
10017            Some(tree_sitter_html::LANGUAGE.into()),
10018        )
10019        .with_injection_query(
10020            r#"
10021            (script_element
10022                (raw_text) @injection.content
10023                (#set! injection.language "javascript"))
10024            "#,
10025        )
10026        .unwrap(),
10027    );
10028
10029    let javascript_language = Arc::new(Language::new(
10030        LanguageConfig {
10031            name: "JavaScript".into(),
10032            brackets: BracketPairConfig {
10033                pairs: vec![
10034                    BracketPair {
10035                        start: "/*".into(),
10036                        end: " */".into(),
10037                        close: true,
10038                        ..Default::default()
10039                    },
10040                    BracketPair {
10041                        start: "{".into(),
10042                        end: "}".into(),
10043                        close: true,
10044                        ..Default::default()
10045                    },
10046                    BracketPair {
10047                        start: "(".into(),
10048                        end: ")".into(),
10049                        close: true,
10050                        ..Default::default()
10051                    },
10052                ],
10053                ..Default::default()
10054            },
10055            autoclose_before: "})]>".into(),
10056            ..Default::default()
10057        },
10058        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10059    ));
10060
10061    cx.language_registry().add(html_language.clone());
10062    cx.language_registry().add(javascript_language);
10063    cx.executor().run_until_parked();
10064
10065    cx.update_buffer(|buffer, cx| {
10066        buffer.set_language(Some(html_language), cx);
10067    });
10068
10069    cx.set_state(
10070        &r#"
10071            <body>ˇ
10072                <script>
10073                    var x = 1;ˇ
10074                </script>
10075            </body>ˇ
10076        "#
10077        .unindent(),
10078    );
10079
10080    // Precondition: different languages are active at different locations.
10081    cx.update_editor(|editor, window, cx| {
10082        let snapshot = editor.snapshot(window, cx);
10083        let cursors = editor.selections.ranges::<usize>(cx);
10084        let languages = cursors
10085            .iter()
10086            .map(|c| snapshot.language_at(c.start).unwrap().name())
10087            .collect::<Vec<_>>();
10088        assert_eq!(
10089            languages,
10090            &["HTML".into(), "JavaScript".into(), "HTML".into()]
10091        );
10092    });
10093
10094    // Angle brackets autoclose in HTML, but not JavaScript.
10095    cx.update_editor(|editor, window, cx| {
10096        editor.handle_input("<", window, cx);
10097        editor.handle_input("a", window, cx);
10098    });
10099    cx.assert_editor_state(
10100        &r#"
10101            <body><aˇ>
10102                <script>
10103                    var x = 1;<aˇ
10104                </script>
10105            </body><aˇ>
10106        "#
10107        .unindent(),
10108    );
10109
10110    // Curly braces and parens autoclose in both HTML and JavaScript.
10111    cx.update_editor(|editor, window, cx| {
10112        editor.handle_input(" b=", window, cx);
10113        editor.handle_input("{", window, cx);
10114        editor.handle_input("c", window, cx);
10115        editor.handle_input("(", window, cx);
10116    });
10117    cx.assert_editor_state(
10118        &r#"
10119            <body><a b={c(ˇ)}>
10120                <script>
10121                    var x = 1;<a b={c(ˇ)}
10122                </script>
10123            </body><a b={c(ˇ)}>
10124        "#
10125        .unindent(),
10126    );
10127
10128    // Brackets that were already autoclosed are skipped.
10129    cx.update_editor(|editor, window, cx| {
10130        editor.handle_input(")", window, cx);
10131        editor.handle_input("d", window, cx);
10132        editor.handle_input("}", window, cx);
10133    });
10134    cx.assert_editor_state(
10135        &r#"
10136            <body><a b={c()d}ˇ>
10137                <script>
10138                    var x = 1;<a b={c()d}ˇ
10139                </script>
10140            </body><a b={c()d}ˇ>
10141        "#
10142        .unindent(),
10143    );
10144    cx.update_editor(|editor, window, cx| {
10145        editor.handle_input(">", window, cx);
10146    });
10147    cx.assert_editor_state(
10148        &r#"
10149            <body><a b={c()d}>ˇ
10150                <script>
10151                    var x = 1;<a b={c()d}>ˇ
10152                </script>
10153            </body><a b={c()d}>ˇ
10154        "#
10155        .unindent(),
10156    );
10157
10158    // Reset
10159    cx.set_state(
10160        &r#"
10161            <body>ˇ
10162                <script>
10163                    var x = 1;ˇ
10164                </script>
10165            </body>ˇ
10166        "#
10167        .unindent(),
10168    );
10169
10170    cx.update_editor(|editor, window, cx| {
10171        editor.handle_input("<", window, cx);
10172    });
10173    cx.assert_editor_state(
10174        &r#"
10175            <body><ˇ>
10176                <script>
10177                    var x = 1;<ˇ
10178                </script>
10179            </body><ˇ>
10180        "#
10181        .unindent(),
10182    );
10183
10184    // When backspacing, the closing angle brackets are removed.
10185    cx.update_editor(|editor, window, cx| {
10186        editor.backspace(&Backspace, window, cx);
10187    });
10188    cx.assert_editor_state(
10189        &r#"
10190            <body>ˇ
10191                <script>
10192                    var x = 1;ˇ
10193                </script>
10194            </body>ˇ
10195        "#
10196        .unindent(),
10197    );
10198
10199    // Block comments autoclose in JavaScript, but not HTML.
10200    cx.update_editor(|editor, window, cx| {
10201        editor.handle_input("/", window, cx);
10202        editor.handle_input("*", window, cx);
10203    });
10204    cx.assert_editor_state(
10205        &r#"
10206            <body>/*ˇ
10207                <script>
10208                    var x = 1;/*ˇ */
10209                </script>
10210            </body>/*ˇ
10211        "#
10212        .unindent(),
10213    );
10214}
10215
10216#[gpui::test]
10217async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
10218    init_test(cx, |_| {});
10219
10220    let mut cx = EditorTestContext::new(cx).await;
10221
10222    let rust_language = Arc::new(
10223        Language::new(
10224            LanguageConfig {
10225                name: "Rust".into(),
10226                brackets: serde_json::from_value(json!([
10227                    { "start": "{", "end": "}", "close": true, "newline": true },
10228                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
10229                ]))
10230                .unwrap(),
10231                autoclose_before: "})]>".into(),
10232                ..Default::default()
10233            },
10234            Some(tree_sitter_rust::LANGUAGE.into()),
10235        )
10236        .with_override_query("(string_literal) @string")
10237        .unwrap(),
10238    );
10239
10240    cx.language_registry().add(rust_language.clone());
10241    cx.update_buffer(|buffer, cx| {
10242        buffer.set_language(Some(rust_language), cx);
10243    });
10244
10245    cx.set_state(
10246        &r#"
10247            let x = ˇ
10248        "#
10249        .unindent(),
10250    );
10251
10252    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
10253    cx.update_editor(|editor, window, cx| {
10254        editor.handle_input("\"", window, cx);
10255    });
10256    cx.assert_editor_state(
10257        &r#"
10258            let x = "ˇ"
10259        "#
10260        .unindent(),
10261    );
10262
10263    // Inserting another quotation mark. The cursor moves across the existing
10264    // automatically-inserted quotation mark.
10265    cx.update_editor(|editor, window, cx| {
10266        editor.handle_input("\"", window, cx);
10267    });
10268    cx.assert_editor_state(
10269        &r#"
10270            let x = ""ˇ
10271        "#
10272        .unindent(),
10273    );
10274
10275    // Reset
10276    cx.set_state(
10277        &r#"
10278            let x = ˇ
10279        "#
10280        .unindent(),
10281    );
10282
10283    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
10284    cx.update_editor(|editor, window, cx| {
10285        editor.handle_input("\"", window, cx);
10286        editor.handle_input(" ", window, cx);
10287        editor.move_left(&Default::default(), window, cx);
10288        editor.handle_input("\\", window, cx);
10289        editor.handle_input("\"", window, cx);
10290    });
10291    cx.assert_editor_state(
10292        &r#"
10293            let x = "\"ˇ "
10294        "#
10295        .unindent(),
10296    );
10297
10298    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
10299    // mark. Nothing is inserted.
10300    cx.update_editor(|editor, window, cx| {
10301        editor.move_right(&Default::default(), window, cx);
10302        editor.handle_input("\"", window, cx);
10303    });
10304    cx.assert_editor_state(
10305        &r#"
10306            let x = "\" "ˇ
10307        "#
10308        .unindent(),
10309    );
10310}
10311
10312#[gpui::test]
10313async fn test_surround_with_pair(cx: &mut TestAppContext) {
10314    init_test(cx, |_| {});
10315
10316    let language = Arc::new(Language::new(
10317        LanguageConfig {
10318            brackets: BracketPairConfig {
10319                pairs: vec![
10320                    BracketPair {
10321                        start: "{".to_string(),
10322                        end: "}".to_string(),
10323                        close: true,
10324                        surround: true,
10325                        newline: true,
10326                    },
10327                    BracketPair {
10328                        start: "/* ".to_string(),
10329                        end: "*/".to_string(),
10330                        close: true,
10331                        surround: true,
10332                        ..Default::default()
10333                    },
10334                ],
10335                ..Default::default()
10336            },
10337            ..Default::default()
10338        },
10339        Some(tree_sitter_rust::LANGUAGE.into()),
10340    ));
10341
10342    let text = r#"
10343        a
10344        b
10345        c
10346    "#
10347    .unindent();
10348
10349    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10350    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10351    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10352    editor
10353        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10354        .await;
10355
10356    editor.update_in(cx, |editor, window, cx| {
10357        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10358            s.select_display_ranges([
10359                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10360                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10361                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
10362            ])
10363        });
10364
10365        editor.handle_input("{", window, cx);
10366        editor.handle_input("{", window, cx);
10367        editor.handle_input("{", window, cx);
10368        assert_eq!(
10369            editor.text(cx),
10370            "
10371                {{{a}}}
10372                {{{b}}}
10373                {{{c}}}
10374            "
10375            .unindent()
10376        );
10377        assert_eq!(
10378            editor.selections.display_ranges(cx),
10379            [
10380                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
10381                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
10382                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
10383            ]
10384        );
10385
10386        editor.undo(&Undo, window, cx);
10387        editor.undo(&Undo, window, cx);
10388        editor.undo(&Undo, window, cx);
10389        assert_eq!(
10390            editor.text(cx),
10391            "
10392                a
10393                b
10394                c
10395            "
10396            .unindent()
10397        );
10398        assert_eq!(
10399            editor.selections.display_ranges(cx),
10400            [
10401                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10402                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10403                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10404            ]
10405        );
10406
10407        // Ensure inserting the first character of a multi-byte bracket pair
10408        // doesn't surround the selections with the bracket.
10409        editor.handle_input("/", window, cx);
10410        assert_eq!(
10411            editor.text(cx),
10412            "
10413                /
10414                /
10415                /
10416            "
10417            .unindent()
10418        );
10419        assert_eq!(
10420            editor.selections.display_ranges(cx),
10421            [
10422                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10423                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10424                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10425            ]
10426        );
10427
10428        editor.undo(&Undo, window, cx);
10429        assert_eq!(
10430            editor.text(cx),
10431            "
10432                a
10433                b
10434                c
10435            "
10436            .unindent()
10437        );
10438        assert_eq!(
10439            editor.selections.display_ranges(cx),
10440            [
10441                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10442                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10443                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10444            ]
10445        );
10446
10447        // Ensure inserting the last character of a multi-byte bracket pair
10448        // doesn't surround the selections with the bracket.
10449        editor.handle_input("*", window, cx);
10450        assert_eq!(
10451            editor.text(cx),
10452            "
10453                *
10454                *
10455                *
10456            "
10457            .unindent()
10458        );
10459        assert_eq!(
10460            editor.selections.display_ranges(cx),
10461            [
10462                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10463                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10464                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10465            ]
10466        );
10467    });
10468}
10469
10470#[gpui::test]
10471async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
10472    init_test(cx, |_| {});
10473
10474    let language = Arc::new(Language::new(
10475        LanguageConfig {
10476            brackets: BracketPairConfig {
10477                pairs: vec![BracketPair {
10478                    start: "{".to_string(),
10479                    end: "}".to_string(),
10480                    close: true,
10481                    surround: true,
10482                    newline: true,
10483                }],
10484                ..Default::default()
10485            },
10486            autoclose_before: "}".to_string(),
10487            ..Default::default()
10488        },
10489        Some(tree_sitter_rust::LANGUAGE.into()),
10490    ));
10491
10492    let text = r#"
10493        a
10494        b
10495        c
10496    "#
10497    .unindent();
10498
10499    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10500    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10501    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10502    editor
10503        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10504        .await;
10505
10506    editor.update_in(cx, |editor, window, cx| {
10507        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10508            s.select_ranges([
10509                Point::new(0, 1)..Point::new(0, 1),
10510                Point::new(1, 1)..Point::new(1, 1),
10511                Point::new(2, 1)..Point::new(2, 1),
10512            ])
10513        });
10514
10515        editor.handle_input("{", window, cx);
10516        editor.handle_input("{", window, cx);
10517        editor.handle_input("_", window, cx);
10518        assert_eq!(
10519            editor.text(cx),
10520            "
10521                a{{_}}
10522                b{{_}}
10523                c{{_}}
10524            "
10525            .unindent()
10526        );
10527        assert_eq!(
10528            editor.selections.ranges::<Point>(cx),
10529            [
10530                Point::new(0, 4)..Point::new(0, 4),
10531                Point::new(1, 4)..Point::new(1, 4),
10532                Point::new(2, 4)..Point::new(2, 4)
10533            ]
10534        );
10535
10536        editor.backspace(&Default::default(), window, cx);
10537        editor.backspace(&Default::default(), window, cx);
10538        assert_eq!(
10539            editor.text(cx),
10540            "
10541                a{}
10542                b{}
10543                c{}
10544            "
10545            .unindent()
10546        );
10547        assert_eq!(
10548            editor.selections.ranges::<Point>(cx),
10549            [
10550                Point::new(0, 2)..Point::new(0, 2),
10551                Point::new(1, 2)..Point::new(1, 2),
10552                Point::new(2, 2)..Point::new(2, 2)
10553            ]
10554        );
10555
10556        editor.delete_to_previous_word_start(&Default::default(), window, cx);
10557        assert_eq!(
10558            editor.text(cx),
10559            "
10560                a
10561                b
10562                c
10563            "
10564            .unindent()
10565        );
10566        assert_eq!(
10567            editor.selections.ranges::<Point>(cx),
10568            [
10569                Point::new(0, 1)..Point::new(0, 1),
10570                Point::new(1, 1)..Point::new(1, 1),
10571                Point::new(2, 1)..Point::new(2, 1)
10572            ]
10573        );
10574    });
10575}
10576
10577#[gpui::test]
10578async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
10579    init_test(cx, |settings| {
10580        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
10581    });
10582
10583    let mut cx = EditorTestContext::new(cx).await;
10584
10585    let language = Arc::new(Language::new(
10586        LanguageConfig {
10587            brackets: BracketPairConfig {
10588                pairs: vec![
10589                    BracketPair {
10590                        start: "{".to_string(),
10591                        end: "}".to_string(),
10592                        close: true,
10593                        surround: true,
10594                        newline: true,
10595                    },
10596                    BracketPair {
10597                        start: "(".to_string(),
10598                        end: ")".to_string(),
10599                        close: true,
10600                        surround: true,
10601                        newline: true,
10602                    },
10603                    BracketPair {
10604                        start: "[".to_string(),
10605                        end: "]".to_string(),
10606                        close: false,
10607                        surround: true,
10608                        newline: true,
10609                    },
10610                ],
10611                ..Default::default()
10612            },
10613            autoclose_before: "})]".to_string(),
10614            ..Default::default()
10615        },
10616        Some(tree_sitter_rust::LANGUAGE.into()),
10617    ));
10618
10619    cx.language_registry().add(language.clone());
10620    cx.update_buffer(|buffer, cx| {
10621        buffer.set_language(Some(language), cx);
10622    });
10623
10624    cx.set_state(
10625        &"
10626            {(ˇ)}
10627            [[ˇ]]
10628            {(ˇ)}
10629        "
10630        .unindent(),
10631    );
10632
10633    cx.update_editor(|editor, window, cx| {
10634        editor.backspace(&Default::default(), window, cx);
10635        editor.backspace(&Default::default(), window, cx);
10636    });
10637
10638    cx.assert_editor_state(
10639        &"
10640            ˇ
10641            ˇ]]
10642            ˇ
10643        "
10644        .unindent(),
10645    );
10646
10647    cx.update_editor(|editor, window, cx| {
10648        editor.handle_input("{", window, cx);
10649        editor.handle_input("{", window, cx);
10650        editor.move_right(&MoveRight, window, cx);
10651        editor.move_right(&MoveRight, window, cx);
10652        editor.move_left(&MoveLeft, window, cx);
10653        editor.move_left(&MoveLeft, window, cx);
10654        editor.backspace(&Default::default(), window, cx);
10655    });
10656
10657    cx.assert_editor_state(
10658        &"
10659            {ˇ}
10660            {ˇ}]]
10661            {ˇ}
10662        "
10663        .unindent(),
10664    );
10665
10666    cx.update_editor(|editor, window, cx| {
10667        editor.backspace(&Default::default(), window, cx);
10668    });
10669
10670    cx.assert_editor_state(
10671        &"
10672            ˇ
10673            ˇ]]
10674            ˇ
10675        "
10676        .unindent(),
10677    );
10678}
10679
10680#[gpui::test]
10681async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
10682    init_test(cx, |_| {});
10683
10684    let language = Arc::new(Language::new(
10685        LanguageConfig::default(),
10686        Some(tree_sitter_rust::LANGUAGE.into()),
10687    ));
10688
10689    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
10690    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10691    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10692    editor
10693        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10694        .await;
10695
10696    editor.update_in(cx, |editor, window, cx| {
10697        editor.set_auto_replace_emoji_shortcode(true);
10698
10699        editor.handle_input("Hello ", window, cx);
10700        editor.handle_input(":wave", window, cx);
10701        assert_eq!(editor.text(cx), "Hello :wave".unindent());
10702
10703        editor.handle_input(":", window, cx);
10704        assert_eq!(editor.text(cx), "Hello 👋".unindent());
10705
10706        editor.handle_input(" :smile", window, cx);
10707        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
10708
10709        editor.handle_input(":", window, cx);
10710        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
10711
10712        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
10713        editor.handle_input(":wave", window, cx);
10714        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
10715
10716        editor.handle_input(":", window, cx);
10717        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
10718
10719        editor.handle_input(":1", window, cx);
10720        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
10721
10722        editor.handle_input(":", window, cx);
10723        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
10724
10725        // Ensure shortcode does not get replaced when it is part of a word
10726        editor.handle_input(" Test:wave", window, cx);
10727        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
10728
10729        editor.handle_input(":", window, cx);
10730        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
10731
10732        editor.set_auto_replace_emoji_shortcode(false);
10733
10734        // Ensure shortcode does not get replaced when auto replace is off
10735        editor.handle_input(" :wave", window, cx);
10736        assert_eq!(
10737            editor.text(cx),
10738            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
10739        );
10740
10741        editor.handle_input(":", window, cx);
10742        assert_eq!(
10743            editor.text(cx),
10744            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
10745        );
10746    });
10747}
10748
10749#[gpui::test]
10750async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
10751    init_test(cx, |_| {});
10752
10753    let (text, insertion_ranges) = marked_text_ranges(
10754        indoc! {"
10755            ˇ
10756        "},
10757        false,
10758    );
10759
10760    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
10761    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10762
10763    _ = editor.update_in(cx, |editor, window, cx| {
10764        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
10765
10766        editor
10767            .insert_snippet(&insertion_ranges, snippet, window, cx)
10768            .unwrap();
10769
10770        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
10771            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
10772            assert_eq!(editor.text(cx), expected_text);
10773            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
10774        }
10775
10776        assert(
10777            editor,
10778            cx,
10779            indoc! {"
10780            type «» =•
10781            "},
10782        );
10783
10784        assert!(editor.context_menu_visible(), "There should be a matches");
10785    });
10786}
10787
10788#[gpui::test]
10789async fn test_snippets(cx: &mut TestAppContext) {
10790    init_test(cx, |_| {});
10791
10792    let mut cx = EditorTestContext::new(cx).await;
10793
10794    cx.set_state(indoc! {"
10795        a.ˇ b
10796        a.ˇ b
10797        a.ˇ b
10798    "});
10799
10800    cx.update_editor(|editor, window, cx| {
10801        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
10802        let insertion_ranges = editor
10803            .selections
10804            .all(cx)
10805            .iter()
10806            .map(|s| s.range())
10807            .collect::<Vec<_>>();
10808        editor
10809            .insert_snippet(&insertion_ranges, snippet, window, cx)
10810            .unwrap();
10811    });
10812
10813    cx.assert_editor_state(indoc! {"
10814        a.f(«oneˇ», two, «threeˇ») b
10815        a.f(«oneˇ», two, «threeˇ») b
10816        a.f(«oneˇ», two, «threeˇ») b
10817    "});
10818
10819    // Can't move earlier than the first tab stop
10820    cx.update_editor(|editor, window, cx| {
10821        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
10822    });
10823    cx.assert_editor_state(indoc! {"
10824        a.f(«oneˇ», two, «threeˇ») b
10825        a.f(«oneˇ», two, «threeˇ») b
10826        a.f(«oneˇ», two, «threeˇ») b
10827    "});
10828
10829    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10830    cx.assert_editor_state(indoc! {"
10831        a.f(one, «twoˇ», three) b
10832        a.f(one, «twoˇ», three) b
10833        a.f(one, «twoˇ», three) b
10834    "});
10835
10836    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
10837    cx.assert_editor_state(indoc! {"
10838        a.f(«oneˇ», two, «threeˇ») b
10839        a.f(«oneˇ», two, «threeˇ») b
10840        a.f(«oneˇ», two, «threeˇ») b
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        a.f(one, «twoˇ», three) b
10846        a.f(one, «twoˇ», three) b
10847        a.f(one, «twoˇ», three) b
10848    "});
10849    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10850    cx.assert_editor_state(indoc! {"
10851        a.f(one, two, three)ˇ b
10852        a.f(one, two, three)ˇ b
10853        a.f(one, two, three)ˇ b
10854    "});
10855
10856    // As soon as the last tab stop is reached, snippet state is gone
10857    cx.update_editor(|editor, window, cx| {
10858        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
10859    });
10860    cx.assert_editor_state(indoc! {"
10861        a.f(one, two, three)ˇ b
10862        a.f(one, two, three)ˇ b
10863        a.f(one, two, three)ˇ b
10864    "});
10865}
10866
10867#[gpui::test]
10868async fn test_snippet_indentation(cx: &mut TestAppContext) {
10869    init_test(cx, |_| {});
10870
10871    let mut cx = EditorTestContext::new(cx).await;
10872
10873    cx.update_editor(|editor, window, cx| {
10874        let snippet = Snippet::parse(indoc! {"
10875            /*
10876             * Multiline comment with leading indentation
10877             *
10878             * $1
10879             */
10880            $0"})
10881        .unwrap();
10882        let insertion_ranges = editor
10883            .selections
10884            .all(cx)
10885            .iter()
10886            .map(|s| s.range())
10887            .collect::<Vec<_>>();
10888        editor
10889            .insert_snippet(&insertion_ranges, snippet, window, cx)
10890            .unwrap();
10891    });
10892
10893    cx.assert_editor_state(indoc! {"
10894        /*
10895         * Multiline comment with leading indentation
10896         *
10897         * ˇ
10898         */
10899    "});
10900
10901    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10902    cx.assert_editor_state(indoc! {"
10903        /*
10904         * Multiline comment with leading indentation
10905         *
10906         *•
10907         */
10908        ˇ"});
10909}
10910
10911#[gpui::test]
10912async fn test_document_format_during_save(cx: &mut TestAppContext) {
10913    init_test(cx, |_| {});
10914
10915    let fs = FakeFs::new(cx.executor());
10916    fs.insert_file(path!("/file.rs"), Default::default()).await;
10917
10918    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
10919
10920    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10921    language_registry.add(rust_lang());
10922    let mut fake_servers = language_registry.register_fake_lsp(
10923        "Rust",
10924        FakeLspAdapter {
10925            capabilities: lsp::ServerCapabilities {
10926                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10927                ..Default::default()
10928            },
10929            ..Default::default()
10930        },
10931    );
10932
10933    let buffer = project
10934        .update(cx, |project, cx| {
10935            project.open_local_buffer(path!("/file.rs"), cx)
10936        })
10937        .await
10938        .unwrap();
10939
10940    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10941    let (editor, cx) = cx.add_window_view(|window, cx| {
10942        build_editor_with_project(project.clone(), buffer, window, cx)
10943    });
10944    editor.update_in(cx, |editor, window, cx| {
10945        editor.set_text("one\ntwo\nthree\n", window, cx)
10946    });
10947    assert!(cx.read(|cx| editor.is_dirty(cx)));
10948
10949    cx.executor().start_waiting();
10950    let fake_server = fake_servers.next().await.unwrap();
10951
10952    {
10953        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10954            move |params, _| async move {
10955                assert_eq!(
10956                    params.text_document.uri,
10957                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
10958                );
10959                assert_eq!(params.options.tab_size, 4);
10960                Ok(Some(vec![lsp::TextEdit::new(
10961                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10962                    ", ".to_string(),
10963                )]))
10964            },
10965        );
10966        let save = editor
10967            .update_in(cx, |editor, window, cx| {
10968                editor.save(
10969                    SaveOptions {
10970                        format: true,
10971                        autosave: false,
10972                    },
10973                    project.clone(),
10974                    window,
10975                    cx,
10976                )
10977            })
10978            .unwrap();
10979        cx.executor().start_waiting();
10980        save.await;
10981
10982        assert_eq!(
10983            editor.update(cx, |editor, cx| editor.text(cx)),
10984            "one, two\nthree\n"
10985        );
10986        assert!(!cx.read(|cx| editor.is_dirty(cx)));
10987    }
10988
10989    {
10990        editor.update_in(cx, |editor, window, cx| {
10991            editor.set_text("one\ntwo\nthree\n", window, cx)
10992        });
10993        assert!(cx.read(|cx| editor.is_dirty(cx)));
10994
10995        // Ensure we can still save even if formatting hangs.
10996        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10997            move |params, _| async move {
10998                assert_eq!(
10999                    params.text_document.uri,
11000                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11001                );
11002                futures::future::pending::<()>().await;
11003                unreachable!()
11004            },
11005        );
11006        let save = editor
11007            .update_in(cx, |editor, window, cx| {
11008                editor.save(
11009                    SaveOptions {
11010                        format: true,
11011                        autosave: false,
11012                    },
11013                    project.clone(),
11014                    window,
11015                    cx,
11016                )
11017            })
11018            .unwrap();
11019        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11020        cx.executor().start_waiting();
11021        save.await;
11022        assert_eq!(
11023            editor.update(cx, |editor, cx| editor.text(cx)),
11024            "one\ntwo\nthree\n"
11025        );
11026    }
11027
11028    // Set rust language override and assert overridden tabsize is sent to language server
11029    update_test_language_settings(cx, |settings| {
11030        settings.languages.0.insert(
11031            "Rust".into(),
11032            LanguageSettingsContent {
11033                tab_size: NonZeroU32::new(8),
11034                ..Default::default()
11035            },
11036        );
11037    });
11038
11039    {
11040        editor.update_in(cx, |editor, window, cx| {
11041            editor.set_text("somehting_new\n", window, cx)
11042        });
11043        assert!(cx.read(|cx| editor.is_dirty(cx)));
11044        let _formatting_request_signal = fake_server
11045            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
11046                assert_eq!(
11047                    params.text_document.uri,
11048                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11049                );
11050                assert_eq!(params.options.tab_size, 8);
11051                Ok(Some(vec![]))
11052            });
11053        let save = editor
11054            .update_in(cx, |editor, window, cx| {
11055                editor.save(
11056                    SaveOptions {
11057                        format: true,
11058                        autosave: false,
11059                    },
11060                    project.clone(),
11061                    window,
11062                    cx,
11063                )
11064            })
11065            .unwrap();
11066        cx.executor().start_waiting();
11067        save.await;
11068    }
11069}
11070
11071#[gpui::test]
11072async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
11073    init_test(cx, |settings| {
11074        settings.defaults.ensure_final_newline_on_save = Some(false);
11075    });
11076
11077    let fs = FakeFs::new(cx.executor());
11078    fs.insert_file(path!("/file.txt"), "foo".into()).await;
11079
11080    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
11081
11082    let buffer = project
11083        .update(cx, |project, cx| {
11084            project.open_local_buffer(path!("/file.txt"), cx)
11085        })
11086        .await
11087        .unwrap();
11088
11089    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11090    let (editor, cx) = cx.add_window_view(|window, cx| {
11091        build_editor_with_project(project.clone(), buffer, window, cx)
11092    });
11093    editor.update_in(cx, |editor, window, cx| {
11094        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
11095            s.select_ranges([0..0])
11096        });
11097    });
11098    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11099
11100    editor.update_in(cx, |editor, window, cx| {
11101        editor.handle_input("\n", window, cx)
11102    });
11103    cx.run_until_parked();
11104    save(&editor, &project, cx).await;
11105    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11106
11107    editor.update_in(cx, |editor, window, cx| {
11108        editor.undo(&Default::default(), window, cx);
11109    });
11110    save(&editor, &project, cx).await;
11111    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11112
11113    editor.update_in(cx, |editor, window, cx| {
11114        editor.redo(&Default::default(), window, cx);
11115    });
11116    cx.run_until_parked();
11117    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11118
11119    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
11120        let save = editor
11121            .update_in(cx, |editor, window, cx| {
11122                editor.save(
11123                    SaveOptions {
11124                        format: true,
11125                        autosave: false,
11126                    },
11127                    project.clone(),
11128                    window,
11129                    cx,
11130                )
11131            })
11132            .unwrap();
11133        cx.executor().start_waiting();
11134        save.await;
11135        assert!(!cx.read(|cx| editor.is_dirty(cx)));
11136    }
11137}
11138
11139#[gpui::test]
11140async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
11141    init_test(cx, |_| {});
11142
11143    let cols = 4;
11144    let rows = 10;
11145    let sample_text_1 = sample_text(rows, cols, 'a');
11146    assert_eq!(
11147        sample_text_1,
11148        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11149    );
11150    let sample_text_2 = sample_text(rows, cols, 'l');
11151    assert_eq!(
11152        sample_text_2,
11153        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11154    );
11155    let sample_text_3 = sample_text(rows, cols, 'v');
11156    assert_eq!(
11157        sample_text_3,
11158        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11159    );
11160
11161    let fs = FakeFs::new(cx.executor());
11162    fs.insert_tree(
11163        path!("/a"),
11164        json!({
11165            "main.rs": sample_text_1,
11166            "other.rs": sample_text_2,
11167            "lib.rs": sample_text_3,
11168        }),
11169    )
11170    .await;
11171
11172    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11173    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11174    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11175
11176    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11177    language_registry.add(rust_lang());
11178    let mut fake_servers = language_registry.register_fake_lsp(
11179        "Rust",
11180        FakeLspAdapter {
11181            capabilities: lsp::ServerCapabilities {
11182                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11183                ..Default::default()
11184            },
11185            ..Default::default()
11186        },
11187    );
11188
11189    let worktree = project.update(cx, |project, cx| {
11190        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
11191        assert_eq!(worktrees.len(), 1);
11192        worktrees.pop().unwrap()
11193    });
11194    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11195
11196    let buffer_1 = project
11197        .update(cx, |project, cx| {
11198            project.open_buffer((worktree_id, "main.rs"), cx)
11199        })
11200        .await
11201        .unwrap();
11202    let buffer_2 = project
11203        .update(cx, |project, cx| {
11204            project.open_buffer((worktree_id, "other.rs"), cx)
11205        })
11206        .await
11207        .unwrap();
11208    let buffer_3 = project
11209        .update(cx, |project, cx| {
11210            project.open_buffer((worktree_id, "lib.rs"), cx)
11211        })
11212        .await
11213        .unwrap();
11214
11215    let multi_buffer = cx.new(|cx| {
11216        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11217        multi_buffer.push_excerpts(
11218            buffer_1.clone(),
11219            [
11220                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11221                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11222                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11223            ],
11224            cx,
11225        );
11226        multi_buffer.push_excerpts(
11227            buffer_2.clone(),
11228            [
11229                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11230                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11231                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11232            ],
11233            cx,
11234        );
11235        multi_buffer.push_excerpts(
11236            buffer_3.clone(),
11237            [
11238                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11239                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11240                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11241            ],
11242            cx,
11243        );
11244        multi_buffer
11245    });
11246    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
11247        Editor::new(
11248            EditorMode::full(),
11249            multi_buffer,
11250            Some(project.clone()),
11251            window,
11252            cx,
11253        )
11254    });
11255
11256    multi_buffer_editor.update_in(cx, |editor, window, cx| {
11257        editor.change_selections(
11258            SelectionEffects::scroll(Autoscroll::Next),
11259            window,
11260            cx,
11261            |s| s.select_ranges(Some(1..2)),
11262        );
11263        editor.insert("|one|two|three|", window, cx);
11264    });
11265    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
11266    multi_buffer_editor.update_in(cx, |editor, window, cx| {
11267        editor.change_selections(
11268            SelectionEffects::scroll(Autoscroll::Next),
11269            window,
11270            cx,
11271            |s| s.select_ranges(Some(60..70)),
11272        );
11273        editor.insert("|four|five|six|", window, cx);
11274    });
11275    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
11276
11277    // First two buffers should be edited, but not the third one.
11278    assert_eq!(
11279        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11280        "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}",
11281    );
11282    buffer_1.update(cx, |buffer, _| {
11283        assert!(buffer.is_dirty());
11284        assert_eq!(
11285            buffer.text(),
11286            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
11287        )
11288    });
11289    buffer_2.update(cx, |buffer, _| {
11290        assert!(buffer.is_dirty());
11291        assert_eq!(
11292            buffer.text(),
11293            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
11294        )
11295    });
11296    buffer_3.update(cx, |buffer, _| {
11297        assert!(!buffer.is_dirty());
11298        assert_eq!(buffer.text(), sample_text_3,)
11299    });
11300    cx.executor().run_until_parked();
11301
11302    cx.executor().start_waiting();
11303    let save = multi_buffer_editor
11304        .update_in(cx, |editor, window, cx| {
11305            editor.save(
11306                SaveOptions {
11307                    format: true,
11308                    autosave: false,
11309                },
11310                project.clone(),
11311                window,
11312                cx,
11313            )
11314        })
11315        .unwrap();
11316
11317    let fake_server = fake_servers.next().await.unwrap();
11318    fake_server
11319        .server
11320        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
11321            Ok(Some(vec![lsp::TextEdit::new(
11322                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11323                format!("[{} formatted]", params.text_document.uri),
11324            )]))
11325        })
11326        .detach();
11327    save.await;
11328
11329    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
11330    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
11331    assert_eq!(
11332        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11333        uri!(
11334            "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}"
11335        ),
11336    );
11337    buffer_1.update(cx, |buffer, _| {
11338        assert!(!buffer.is_dirty());
11339        assert_eq!(
11340            buffer.text(),
11341            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
11342        )
11343    });
11344    buffer_2.update(cx, |buffer, _| {
11345        assert!(!buffer.is_dirty());
11346        assert_eq!(
11347            buffer.text(),
11348            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
11349        )
11350    });
11351    buffer_3.update(cx, |buffer, _| {
11352        assert!(!buffer.is_dirty());
11353        assert_eq!(buffer.text(), sample_text_3,)
11354    });
11355}
11356
11357#[gpui::test]
11358async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
11359    init_test(cx, |_| {});
11360
11361    let fs = FakeFs::new(cx.executor());
11362    fs.insert_tree(
11363        path!("/dir"),
11364        json!({
11365            "file1.rs": "fn main() { println!(\"hello\"); }",
11366            "file2.rs": "fn test() { println!(\"test\"); }",
11367            "file3.rs": "fn other() { println!(\"other\"); }\n",
11368        }),
11369    )
11370    .await;
11371
11372    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
11373    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11374    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11375
11376    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11377    language_registry.add(rust_lang());
11378
11379    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
11380    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11381
11382    // Open three buffers
11383    let buffer_1 = project
11384        .update(cx, |project, cx| {
11385            project.open_buffer((worktree_id, "file1.rs"), cx)
11386        })
11387        .await
11388        .unwrap();
11389    let buffer_2 = project
11390        .update(cx, |project, cx| {
11391            project.open_buffer((worktree_id, "file2.rs"), cx)
11392        })
11393        .await
11394        .unwrap();
11395    let buffer_3 = project
11396        .update(cx, |project, cx| {
11397            project.open_buffer((worktree_id, "file3.rs"), cx)
11398        })
11399        .await
11400        .unwrap();
11401
11402    // Create a multi-buffer with all three buffers
11403    let multi_buffer = cx.new(|cx| {
11404        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11405        multi_buffer.push_excerpts(
11406            buffer_1.clone(),
11407            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11408            cx,
11409        );
11410        multi_buffer.push_excerpts(
11411            buffer_2.clone(),
11412            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11413            cx,
11414        );
11415        multi_buffer.push_excerpts(
11416            buffer_3.clone(),
11417            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11418            cx,
11419        );
11420        multi_buffer
11421    });
11422
11423    let editor = cx.new_window_entity(|window, cx| {
11424        Editor::new(
11425            EditorMode::full(),
11426            multi_buffer,
11427            Some(project.clone()),
11428            window,
11429            cx,
11430        )
11431    });
11432
11433    // Edit only the first buffer
11434    editor.update_in(cx, |editor, window, cx| {
11435        editor.change_selections(
11436            SelectionEffects::scroll(Autoscroll::Next),
11437            window,
11438            cx,
11439            |s| s.select_ranges(Some(10..10)),
11440        );
11441        editor.insert("// edited", window, cx);
11442    });
11443
11444    // Verify that only buffer 1 is dirty
11445    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
11446    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11447    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11448
11449    // Get write counts after file creation (files were created with initial content)
11450    // We expect each file to have been written once during creation
11451    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
11452    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
11453    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
11454
11455    // Perform autosave
11456    let save_task = editor.update_in(cx, |editor, window, cx| {
11457        editor.save(
11458            SaveOptions {
11459                format: true,
11460                autosave: true,
11461            },
11462            project.clone(),
11463            window,
11464            cx,
11465        )
11466    });
11467    save_task.await.unwrap();
11468
11469    // Only the dirty buffer should have been saved
11470    assert_eq!(
11471        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
11472        1,
11473        "Buffer 1 was dirty, so it should have been written once during autosave"
11474    );
11475    assert_eq!(
11476        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
11477        0,
11478        "Buffer 2 was clean, so it should not have been written during autosave"
11479    );
11480    assert_eq!(
11481        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
11482        0,
11483        "Buffer 3 was clean, so it should not have been written during autosave"
11484    );
11485
11486    // Verify buffer states after autosave
11487    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11488    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11489    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11490
11491    // Now perform a manual save (format = true)
11492    let save_task = editor.update_in(cx, |editor, window, cx| {
11493        editor.save(
11494            SaveOptions {
11495                format: true,
11496                autosave: false,
11497            },
11498            project.clone(),
11499            window,
11500            cx,
11501        )
11502    });
11503    save_task.await.unwrap();
11504
11505    // During manual save, clean buffers don't get written to disk
11506    // They just get did_save called for language server notifications
11507    assert_eq!(
11508        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
11509        1,
11510        "Buffer 1 should only have been written once total (during autosave, not manual save)"
11511    );
11512    assert_eq!(
11513        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
11514        0,
11515        "Buffer 2 should not have been written at all"
11516    );
11517    assert_eq!(
11518        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
11519        0,
11520        "Buffer 3 should not have been written at all"
11521    );
11522}
11523
11524async fn setup_range_format_test(
11525    cx: &mut TestAppContext,
11526) -> (
11527    Entity<Project>,
11528    Entity<Editor>,
11529    &mut gpui::VisualTestContext,
11530    lsp::FakeLanguageServer,
11531) {
11532    init_test(cx, |_| {});
11533
11534    let fs = FakeFs::new(cx.executor());
11535    fs.insert_file(path!("/file.rs"), Default::default()).await;
11536
11537    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11538
11539    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11540    language_registry.add(rust_lang());
11541    let mut fake_servers = language_registry.register_fake_lsp(
11542        "Rust",
11543        FakeLspAdapter {
11544            capabilities: lsp::ServerCapabilities {
11545                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
11546                ..lsp::ServerCapabilities::default()
11547            },
11548            ..FakeLspAdapter::default()
11549        },
11550    );
11551
11552    let buffer = project
11553        .update(cx, |project, cx| {
11554            project.open_local_buffer(path!("/file.rs"), cx)
11555        })
11556        .await
11557        .unwrap();
11558
11559    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11560    let (editor, cx) = cx.add_window_view(|window, cx| {
11561        build_editor_with_project(project.clone(), buffer, window, cx)
11562    });
11563
11564    cx.executor().start_waiting();
11565    let fake_server = fake_servers.next().await.unwrap();
11566
11567    (project, editor, cx, fake_server)
11568}
11569
11570#[gpui::test]
11571async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
11572    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11573
11574    editor.update_in(cx, |editor, window, cx| {
11575        editor.set_text("one\ntwo\nthree\n", window, cx)
11576    });
11577    assert!(cx.read(|cx| editor.is_dirty(cx)));
11578
11579    let save = editor
11580        .update_in(cx, |editor, window, cx| {
11581            editor.save(
11582                SaveOptions {
11583                    format: true,
11584                    autosave: false,
11585                },
11586                project.clone(),
11587                window,
11588                cx,
11589            )
11590        })
11591        .unwrap();
11592    fake_server
11593        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
11594            assert_eq!(
11595                params.text_document.uri,
11596                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11597            );
11598            assert_eq!(params.options.tab_size, 4);
11599            Ok(Some(vec![lsp::TextEdit::new(
11600                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11601                ", ".to_string(),
11602            )]))
11603        })
11604        .next()
11605        .await;
11606    cx.executor().start_waiting();
11607    save.await;
11608    assert_eq!(
11609        editor.update(cx, |editor, cx| editor.text(cx)),
11610        "one, two\nthree\n"
11611    );
11612    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11613}
11614
11615#[gpui::test]
11616async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
11617    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11618
11619    editor.update_in(cx, |editor, window, cx| {
11620        editor.set_text("one\ntwo\nthree\n", window, cx)
11621    });
11622    assert!(cx.read(|cx| editor.is_dirty(cx)));
11623
11624    // Test that save still works when formatting hangs
11625    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
11626        move |params, _| async move {
11627            assert_eq!(
11628                params.text_document.uri,
11629                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11630            );
11631            futures::future::pending::<()>().await;
11632            unreachable!()
11633        },
11634    );
11635    let save = editor
11636        .update_in(cx, |editor, window, cx| {
11637            editor.save(
11638                SaveOptions {
11639                    format: true,
11640                    autosave: false,
11641                },
11642                project.clone(),
11643                window,
11644                cx,
11645            )
11646        })
11647        .unwrap();
11648    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11649    cx.executor().start_waiting();
11650    save.await;
11651    assert_eq!(
11652        editor.update(cx, |editor, cx| editor.text(cx)),
11653        "one\ntwo\nthree\n"
11654    );
11655    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11656}
11657
11658#[gpui::test]
11659async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
11660    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11661
11662    // Buffer starts clean, no formatting should be requested
11663    let save = editor
11664        .update_in(cx, |editor, window, cx| {
11665            editor.save(
11666                SaveOptions {
11667                    format: false,
11668                    autosave: false,
11669                },
11670                project.clone(),
11671                window,
11672                cx,
11673            )
11674        })
11675        .unwrap();
11676    let _pending_format_request = fake_server
11677        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
11678            panic!("Should not be invoked");
11679        })
11680        .next();
11681    cx.executor().start_waiting();
11682    save.await;
11683    cx.run_until_parked();
11684}
11685
11686#[gpui::test]
11687async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
11688    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11689
11690    // Set Rust language override and assert overridden tabsize is sent to language server
11691    update_test_language_settings(cx, |settings| {
11692        settings.languages.0.insert(
11693            "Rust".into(),
11694            LanguageSettingsContent {
11695                tab_size: NonZeroU32::new(8),
11696                ..Default::default()
11697            },
11698        );
11699    });
11700
11701    editor.update_in(cx, |editor, window, cx| {
11702        editor.set_text("something_new\n", window, cx)
11703    });
11704    assert!(cx.read(|cx| editor.is_dirty(cx)));
11705    let save = editor
11706        .update_in(cx, |editor, window, cx| {
11707            editor.save(
11708                SaveOptions {
11709                    format: true,
11710                    autosave: false,
11711                },
11712                project.clone(),
11713                window,
11714                cx,
11715            )
11716        })
11717        .unwrap();
11718    fake_server
11719        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
11720            assert_eq!(
11721                params.text_document.uri,
11722                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11723            );
11724            assert_eq!(params.options.tab_size, 8);
11725            Ok(Some(Vec::new()))
11726        })
11727        .next()
11728        .await;
11729    save.await;
11730}
11731
11732#[gpui::test]
11733async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
11734    init_test(cx, |settings| {
11735        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
11736            Formatter::LanguageServer { name: None },
11737        )))
11738    });
11739
11740    let fs = FakeFs::new(cx.executor());
11741    fs.insert_file(path!("/file.rs"), Default::default()).await;
11742
11743    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11744
11745    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11746    language_registry.add(Arc::new(Language::new(
11747        LanguageConfig {
11748            name: "Rust".into(),
11749            matcher: LanguageMatcher {
11750                path_suffixes: vec!["rs".to_string()],
11751                ..Default::default()
11752            },
11753            ..LanguageConfig::default()
11754        },
11755        Some(tree_sitter_rust::LANGUAGE.into()),
11756    )));
11757    update_test_language_settings(cx, |settings| {
11758        // Enable Prettier formatting for the same buffer, and ensure
11759        // LSP is called instead of Prettier.
11760        settings.defaults.prettier = Some(PrettierSettings {
11761            allowed: true,
11762            ..PrettierSettings::default()
11763        });
11764    });
11765    let mut fake_servers = language_registry.register_fake_lsp(
11766        "Rust",
11767        FakeLspAdapter {
11768            capabilities: lsp::ServerCapabilities {
11769                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11770                ..Default::default()
11771            },
11772            ..Default::default()
11773        },
11774    );
11775
11776    let buffer = project
11777        .update(cx, |project, cx| {
11778            project.open_local_buffer(path!("/file.rs"), cx)
11779        })
11780        .await
11781        .unwrap();
11782
11783    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11784    let (editor, cx) = cx.add_window_view(|window, cx| {
11785        build_editor_with_project(project.clone(), buffer, window, cx)
11786    });
11787    editor.update_in(cx, |editor, window, cx| {
11788        editor.set_text("one\ntwo\nthree\n", window, cx)
11789    });
11790
11791    cx.executor().start_waiting();
11792    let fake_server = fake_servers.next().await.unwrap();
11793
11794    let format = editor
11795        .update_in(cx, |editor, window, cx| {
11796            editor.perform_format(
11797                project.clone(),
11798                FormatTrigger::Manual,
11799                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
11800                window,
11801                cx,
11802            )
11803        })
11804        .unwrap();
11805    fake_server
11806        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
11807            assert_eq!(
11808                params.text_document.uri,
11809                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11810            );
11811            assert_eq!(params.options.tab_size, 4);
11812            Ok(Some(vec![lsp::TextEdit::new(
11813                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11814                ", ".to_string(),
11815            )]))
11816        })
11817        .next()
11818        .await;
11819    cx.executor().start_waiting();
11820    format.await;
11821    assert_eq!(
11822        editor.update(cx, |editor, cx| editor.text(cx)),
11823        "one, two\nthree\n"
11824    );
11825
11826    editor.update_in(cx, |editor, window, cx| {
11827        editor.set_text("one\ntwo\nthree\n", window, cx)
11828    });
11829    // Ensure we don't lock if formatting hangs.
11830    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11831        move |params, _| async move {
11832            assert_eq!(
11833                params.text_document.uri,
11834                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11835            );
11836            futures::future::pending::<()>().await;
11837            unreachable!()
11838        },
11839    );
11840    let format = editor
11841        .update_in(cx, |editor, window, cx| {
11842            editor.perform_format(
11843                project,
11844                FormatTrigger::Manual,
11845                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
11846                window,
11847                cx,
11848            )
11849        })
11850        .unwrap();
11851    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11852    cx.executor().start_waiting();
11853    format.await;
11854    assert_eq!(
11855        editor.update(cx, |editor, cx| editor.text(cx)),
11856        "one\ntwo\nthree\n"
11857    );
11858}
11859
11860#[gpui::test]
11861async fn test_multiple_formatters(cx: &mut TestAppContext) {
11862    init_test(cx, |settings| {
11863        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
11864        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
11865            Formatter::LanguageServer { name: None },
11866            Formatter::CodeActions(
11867                [
11868                    ("code-action-1".into(), true),
11869                    ("code-action-2".into(), true),
11870                ]
11871                .into_iter()
11872                .collect(),
11873            ),
11874        ])))
11875    });
11876
11877    let fs = FakeFs::new(cx.executor());
11878    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
11879        .await;
11880
11881    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11882    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11883    language_registry.add(rust_lang());
11884
11885    let mut fake_servers = language_registry.register_fake_lsp(
11886        "Rust",
11887        FakeLspAdapter {
11888            capabilities: lsp::ServerCapabilities {
11889                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11890                execute_command_provider: Some(lsp::ExecuteCommandOptions {
11891                    commands: vec!["the-command-for-code-action-1".into()],
11892                    ..Default::default()
11893                }),
11894                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
11895                ..Default::default()
11896            },
11897            ..Default::default()
11898        },
11899    );
11900
11901    let buffer = project
11902        .update(cx, |project, cx| {
11903            project.open_local_buffer(path!("/file.rs"), cx)
11904        })
11905        .await
11906        .unwrap();
11907
11908    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11909    let (editor, cx) = cx.add_window_view(|window, cx| {
11910        build_editor_with_project(project.clone(), buffer, window, cx)
11911    });
11912
11913    cx.executor().start_waiting();
11914
11915    let fake_server = fake_servers.next().await.unwrap();
11916    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11917        move |_params, _| async move {
11918            Ok(Some(vec![lsp::TextEdit::new(
11919                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
11920                "applied-formatting\n".to_string(),
11921            )]))
11922        },
11923    );
11924    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
11925        move |params, _| async move {
11926            assert_eq!(
11927                params.context.only,
11928                Some(vec!["code-action-1".into(), "code-action-2".into()])
11929            );
11930            let uri = lsp::Uri::from_file_path(path!("/file.rs")).unwrap();
11931            Ok(Some(vec![
11932                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
11933                    kind: Some("code-action-1".into()),
11934                    edit: Some(lsp::WorkspaceEdit::new(
11935                        [(
11936                            uri.clone(),
11937                            vec![lsp::TextEdit::new(
11938                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
11939                                "applied-code-action-1-edit\n".to_string(),
11940                            )],
11941                        )]
11942                        .into_iter()
11943                        .collect(),
11944                    )),
11945                    command: Some(lsp::Command {
11946                        command: "the-command-for-code-action-1".into(),
11947                        ..Default::default()
11948                    }),
11949                    ..Default::default()
11950                }),
11951                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
11952                    kind: Some("code-action-2".into()),
11953                    edit: Some(lsp::WorkspaceEdit::new(
11954                        [(
11955                            uri,
11956                            vec![lsp::TextEdit::new(
11957                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
11958                                "applied-code-action-2-edit\n".to_string(),
11959                            )],
11960                        )]
11961                        .into_iter()
11962                        .collect(),
11963                    )),
11964                    ..Default::default()
11965                }),
11966            ]))
11967        },
11968    );
11969
11970    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
11971        move |params, _| async move { Ok(params) }
11972    });
11973
11974    let command_lock = Arc::new(futures::lock::Mutex::new(()));
11975    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
11976        let fake = fake_server.clone();
11977        let lock = command_lock.clone();
11978        move |params, _| {
11979            assert_eq!(params.command, "the-command-for-code-action-1");
11980            let fake = fake.clone();
11981            let lock = lock.clone();
11982            async move {
11983                lock.lock().await;
11984                fake.server
11985                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
11986                        label: None,
11987                        edit: lsp::WorkspaceEdit {
11988                            changes: Some(
11989                                [(
11990                                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
11991                                    vec![lsp::TextEdit {
11992                                        range: lsp::Range::new(
11993                                            lsp::Position::new(0, 0),
11994                                            lsp::Position::new(0, 0),
11995                                        ),
11996                                        new_text: "applied-code-action-1-command\n".into(),
11997                                    }],
11998                                )]
11999                                .into_iter()
12000                                .collect(),
12001                            ),
12002                            ..Default::default()
12003                        },
12004                    })
12005                    .await
12006                    .into_response()
12007                    .unwrap();
12008                Ok(Some(json!(null)))
12009            }
12010        }
12011    });
12012
12013    cx.executor().start_waiting();
12014    editor
12015        .update_in(cx, |editor, window, cx| {
12016            editor.perform_format(
12017                project.clone(),
12018                FormatTrigger::Manual,
12019                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12020                window,
12021                cx,
12022            )
12023        })
12024        .unwrap()
12025        .await;
12026    editor.update(cx, |editor, cx| {
12027        assert_eq!(
12028            editor.text(cx),
12029            r#"
12030                applied-code-action-2-edit
12031                applied-code-action-1-command
12032                applied-code-action-1-edit
12033                applied-formatting
12034                one
12035                two
12036                three
12037            "#
12038            .unindent()
12039        );
12040    });
12041
12042    editor.update_in(cx, |editor, window, cx| {
12043        editor.undo(&Default::default(), window, cx);
12044        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12045    });
12046
12047    // Perform a manual edit while waiting for an LSP command
12048    // that's being run as part of a formatting code action.
12049    let lock_guard = command_lock.lock().await;
12050    let format = editor
12051        .update_in(cx, |editor, window, cx| {
12052            editor.perform_format(
12053                project.clone(),
12054                FormatTrigger::Manual,
12055                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12056                window,
12057                cx,
12058            )
12059        })
12060        .unwrap();
12061    cx.run_until_parked();
12062    editor.update(cx, |editor, cx| {
12063        assert_eq!(
12064            editor.text(cx),
12065            r#"
12066                applied-code-action-1-edit
12067                applied-formatting
12068                one
12069                two
12070                three
12071            "#
12072            .unindent()
12073        );
12074
12075        editor.buffer.update(cx, |buffer, cx| {
12076            let ix = buffer.len(cx);
12077            buffer.edit([(ix..ix, "edited\n")], None, cx);
12078        });
12079    });
12080
12081    // Allow the LSP command to proceed. Because the buffer was edited,
12082    // the second code action will not be run.
12083    drop(lock_guard);
12084    format.await;
12085    editor.update_in(cx, |editor, window, cx| {
12086        assert_eq!(
12087            editor.text(cx),
12088            r#"
12089                applied-code-action-1-command
12090                applied-code-action-1-edit
12091                applied-formatting
12092                one
12093                two
12094                three
12095                edited
12096            "#
12097            .unindent()
12098        );
12099
12100        // The manual edit is undone first, because it is the last thing the user did
12101        // (even though the command completed afterwards).
12102        editor.undo(&Default::default(), window, cx);
12103        assert_eq!(
12104            editor.text(cx),
12105            r#"
12106                applied-code-action-1-command
12107                applied-code-action-1-edit
12108                applied-formatting
12109                one
12110                two
12111                three
12112            "#
12113            .unindent()
12114        );
12115
12116        // All the formatting (including the command, which completed after the manual edit)
12117        // is undone together.
12118        editor.undo(&Default::default(), window, cx);
12119        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12120    });
12121}
12122
12123#[gpui::test]
12124async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
12125    init_test(cx, |settings| {
12126        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
12127            Formatter::LanguageServer { name: None },
12128        ])))
12129    });
12130
12131    let fs = FakeFs::new(cx.executor());
12132    fs.insert_file(path!("/file.ts"), Default::default()).await;
12133
12134    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12135
12136    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12137    language_registry.add(Arc::new(Language::new(
12138        LanguageConfig {
12139            name: "TypeScript".into(),
12140            matcher: LanguageMatcher {
12141                path_suffixes: vec!["ts".to_string()],
12142                ..Default::default()
12143            },
12144            ..LanguageConfig::default()
12145        },
12146        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12147    )));
12148    update_test_language_settings(cx, |settings| {
12149        settings.defaults.prettier = Some(PrettierSettings {
12150            allowed: true,
12151            ..PrettierSettings::default()
12152        });
12153    });
12154    let mut fake_servers = language_registry.register_fake_lsp(
12155        "TypeScript",
12156        FakeLspAdapter {
12157            capabilities: lsp::ServerCapabilities {
12158                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
12159                ..Default::default()
12160            },
12161            ..Default::default()
12162        },
12163    );
12164
12165    let buffer = project
12166        .update(cx, |project, cx| {
12167            project.open_local_buffer(path!("/file.ts"), cx)
12168        })
12169        .await
12170        .unwrap();
12171
12172    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12173    let (editor, cx) = cx.add_window_view(|window, cx| {
12174        build_editor_with_project(project.clone(), buffer, window, cx)
12175    });
12176    editor.update_in(cx, |editor, window, cx| {
12177        editor.set_text(
12178            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12179            window,
12180            cx,
12181        )
12182    });
12183
12184    cx.executor().start_waiting();
12185    let fake_server = fake_servers.next().await.unwrap();
12186
12187    let format = editor
12188        .update_in(cx, |editor, window, cx| {
12189            editor.perform_code_action_kind(
12190                project.clone(),
12191                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12192                window,
12193                cx,
12194            )
12195        })
12196        .unwrap();
12197    fake_server
12198        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
12199            assert_eq!(
12200                params.text_document.uri,
12201                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12202            );
12203            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
12204                lsp::CodeAction {
12205                    title: "Organize Imports".to_string(),
12206                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
12207                    edit: Some(lsp::WorkspaceEdit {
12208                        changes: Some(
12209                            [(
12210                                params.text_document.uri.clone(),
12211                                vec![lsp::TextEdit::new(
12212                                    lsp::Range::new(
12213                                        lsp::Position::new(1, 0),
12214                                        lsp::Position::new(2, 0),
12215                                    ),
12216                                    "".to_string(),
12217                                )],
12218                            )]
12219                            .into_iter()
12220                            .collect(),
12221                        ),
12222                        ..Default::default()
12223                    }),
12224                    ..Default::default()
12225                },
12226            )]))
12227        })
12228        .next()
12229        .await;
12230    cx.executor().start_waiting();
12231    format.await;
12232    assert_eq!(
12233        editor.update(cx, |editor, cx| editor.text(cx)),
12234        "import { a } from 'module';\n\nconst x = a;\n"
12235    );
12236
12237    editor.update_in(cx, |editor, window, cx| {
12238        editor.set_text(
12239            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12240            window,
12241            cx,
12242        )
12243    });
12244    // Ensure we don't lock if code action hangs.
12245    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
12246        move |params, _| async move {
12247            assert_eq!(
12248                params.text_document.uri,
12249                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12250            );
12251            futures::future::pending::<()>().await;
12252            unreachable!()
12253        },
12254    );
12255    let format = editor
12256        .update_in(cx, |editor, window, cx| {
12257            editor.perform_code_action_kind(
12258                project,
12259                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12260                window,
12261                cx,
12262            )
12263        })
12264        .unwrap();
12265    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
12266    cx.executor().start_waiting();
12267    format.await;
12268    assert_eq!(
12269        editor.update(cx, |editor, cx| editor.text(cx)),
12270        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
12271    );
12272}
12273
12274#[gpui::test]
12275async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
12276    init_test(cx, |_| {});
12277
12278    let mut cx = EditorLspTestContext::new_rust(
12279        lsp::ServerCapabilities {
12280            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12281            ..Default::default()
12282        },
12283        cx,
12284    )
12285    .await;
12286
12287    cx.set_state(indoc! {"
12288        one.twoˇ
12289    "});
12290
12291    // The format request takes a long time. When it completes, it inserts
12292    // a newline and an indent before the `.`
12293    cx.lsp
12294        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
12295            let executor = cx.background_executor().clone();
12296            async move {
12297                executor.timer(Duration::from_millis(100)).await;
12298                Ok(Some(vec![lsp::TextEdit {
12299                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
12300                    new_text: "\n    ".into(),
12301                }]))
12302            }
12303        });
12304
12305    // Submit a format request.
12306    let format_1 = cx
12307        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12308        .unwrap();
12309    cx.executor().run_until_parked();
12310
12311    // Submit a second format request.
12312    let format_2 = cx
12313        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12314        .unwrap();
12315    cx.executor().run_until_parked();
12316
12317    // Wait for both format requests to complete
12318    cx.executor().advance_clock(Duration::from_millis(200));
12319    cx.executor().start_waiting();
12320    format_1.await.unwrap();
12321    cx.executor().start_waiting();
12322    format_2.await.unwrap();
12323
12324    // The formatting edits only happens once.
12325    cx.assert_editor_state(indoc! {"
12326        one
12327            .twoˇ
12328    "});
12329}
12330
12331#[gpui::test]
12332async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
12333    init_test(cx, |settings| {
12334        settings.defaults.formatter = Some(SelectedFormatter::Auto)
12335    });
12336
12337    let mut cx = EditorLspTestContext::new_rust(
12338        lsp::ServerCapabilities {
12339            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12340            ..Default::default()
12341        },
12342        cx,
12343    )
12344    .await;
12345
12346    // Set up a buffer white some trailing whitespace and no trailing newline.
12347    cx.set_state(
12348        &[
12349            "one ",   //
12350            "twoˇ",   //
12351            "three ", //
12352            "four",   //
12353        ]
12354        .join("\n"),
12355    );
12356
12357    // Submit a format request.
12358    let format = cx
12359        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12360        .unwrap();
12361
12362    // Record which buffer changes have been sent to the language server
12363    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
12364    cx.lsp
12365        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
12366            let buffer_changes = buffer_changes.clone();
12367            move |params, _| {
12368                buffer_changes.lock().extend(
12369                    params
12370                        .content_changes
12371                        .into_iter()
12372                        .map(|e| (e.range.unwrap(), e.text)),
12373                );
12374            }
12375        });
12376
12377    // Handle formatting requests to the language server.
12378    cx.lsp
12379        .set_request_handler::<lsp::request::Formatting, _, _>({
12380            let buffer_changes = buffer_changes.clone();
12381            move |_, _| {
12382                // When formatting is requested, trailing whitespace has already been stripped,
12383                // and the trailing newline has already been added.
12384                assert_eq!(
12385                    &buffer_changes.lock()[1..],
12386                    &[
12387                        (
12388                            lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
12389                            "".into()
12390                        ),
12391                        (
12392                            lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
12393                            "".into()
12394                        ),
12395                        (
12396                            lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
12397                            "\n".into()
12398                        ),
12399                    ]
12400                );
12401
12402                // Insert blank lines between each line of the buffer.
12403                async move {
12404                    Ok(Some(vec![
12405                        lsp::TextEdit {
12406                            range: lsp::Range::new(
12407                                lsp::Position::new(1, 0),
12408                                lsp::Position::new(1, 0),
12409                            ),
12410                            new_text: "\n".into(),
12411                        },
12412                        lsp::TextEdit {
12413                            range: lsp::Range::new(
12414                                lsp::Position::new(2, 0),
12415                                lsp::Position::new(2, 0),
12416                            ),
12417                            new_text: "\n".into(),
12418                        },
12419                    ]))
12420                }
12421            }
12422        });
12423
12424    // After formatting the buffer, the trailing whitespace is stripped,
12425    // a newline is appended, and the edits provided by the language server
12426    // have been applied.
12427    format.await.unwrap();
12428    cx.assert_editor_state(
12429        &[
12430            "one",   //
12431            "",      //
12432            "twoˇ",  //
12433            "",      //
12434            "three", //
12435            "four",  //
12436            "",      //
12437        ]
12438        .join("\n"),
12439    );
12440
12441    // Undoing the formatting undoes the trailing whitespace removal, the
12442    // trailing newline, and the LSP edits.
12443    cx.update_buffer(|buffer, cx| buffer.undo(cx));
12444    cx.assert_editor_state(
12445        &[
12446            "one ",   //
12447            "twoˇ",   //
12448            "three ", //
12449            "four",   //
12450        ]
12451        .join("\n"),
12452    );
12453}
12454
12455#[gpui::test]
12456async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
12457    cx: &mut TestAppContext,
12458) {
12459    init_test(cx, |_| {});
12460
12461    cx.update(|cx| {
12462        cx.update_global::<SettingsStore, _>(|settings, cx| {
12463            settings.update_user_settings::<EditorSettings>(cx, |settings| {
12464                settings.auto_signature_help = Some(true);
12465            });
12466        });
12467    });
12468
12469    let mut cx = EditorLspTestContext::new_rust(
12470        lsp::ServerCapabilities {
12471            signature_help_provider: Some(lsp::SignatureHelpOptions {
12472                ..Default::default()
12473            }),
12474            ..Default::default()
12475        },
12476        cx,
12477    )
12478    .await;
12479
12480    let language = Language::new(
12481        LanguageConfig {
12482            name: "Rust".into(),
12483            brackets: BracketPairConfig {
12484                pairs: vec![
12485                    BracketPair {
12486                        start: "{".to_string(),
12487                        end: "}".to_string(),
12488                        close: true,
12489                        surround: true,
12490                        newline: true,
12491                    },
12492                    BracketPair {
12493                        start: "(".to_string(),
12494                        end: ")".to_string(),
12495                        close: true,
12496                        surround: true,
12497                        newline: true,
12498                    },
12499                    BracketPair {
12500                        start: "/*".to_string(),
12501                        end: " */".to_string(),
12502                        close: true,
12503                        surround: true,
12504                        newline: true,
12505                    },
12506                    BracketPair {
12507                        start: "[".to_string(),
12508                        end: "]".to_string(),
12509                        close: false,
12510                        surround: false,
12511                        newline: true,
12512                    },
12513                    BracketPair {
12514                        start: "\"".to_string(),
12515                        end: "\"".to_string(),
12516                        close: true,
12517                        surround: true,
12518                        newline: false,
12519                    },
12520                    BracketPair {
12521                        start: "<".to_string(),
12522                        end: ">".to_string(),
12523                        close: false,
12524                        surround: true,
12525                        newline: true,
12526                    },
12527                ],
12528                ..Default::default()
12529            },
12530            autoclose_before: "})]".to_string(),
12531            ..Default::default()
12532        },
12533        Some(tree_sitter_rust::LANGUAGE.into()),
12534    );
12535    let language = Arc::new(language);
12536
12537    cx.language_registry().add(language.clone());
12538    cx.update_buffer(|buffer, cx| {
12539        buffer.set_language(Some(language), cx);
12540    });
12541
12542    cx.set_state(
12543        &r#"
12544            fn main() {
12545                sampleˇ
12546            }
12547        "#
12548        .unindent(),
12549    );
12550
12551    cx.update_editor(|editor, window, cx| {
12552        editor.handle_input("(", window, cx);
12553    });
12554    cx.assert_editor_state(
12555        &"
12556            fn main() {
12557                sample(ˇ)
12558            }
12559        "
12560        .unindent(),
12561    );
12562
12563    let mocked_response = lsp::SignatureHelp {
12564        signatures: vec![lsp::SignatureInformation {
12565            label: "fn sample(param1: u8, param2: u8)".to_string(),
12566            documentation: None,
12567            parameters: Some(vec![
12568                lsp::ParameterInformation {
12569                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12570                    documentation: None,
12571                },
12572                lsp::ParameterInformation {
12573                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12574                    documentation: None,
12575                },
12576            ]),
12577            active_parameter: None,
12578        }],
12579        active_signature: Some(0),
12580        active_parameter: Some(0),
12581    };
12582    handle_signature_help_request(&mut cx, mocked_response).await;
12583
12584    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12585        .await;
12586
12587    cx.editor(|editor, _, _| {
12588        let signature_help_state = editor.signature_help_state.popover().cloned();
12589        let signature = signature_help_state.unwrap();
12590        assert_eq!(
12591            signature.signatures[signature.current_signature].label,
12592            "fn sample(param1: u8, param2: u8)"
12593        );
12594    });
12595}
12596
12597#[gpui::test]
12598async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
12599    init_test(cx, |_| {});
12600
12601    cx.update(|cx| {
12602        cx.update_global::<SettingsStore, _>(|settings, cx| {
12603            settings.update_user_settings::<EditorSettings>(cx, |settings| {
12604                settings.auto_signature_help = Some(false);
12605                settings.show_signature_help_after_edits = Some(false);
12606            });
12607        });
12608    });
12609
12610    let mut cx = EditorLspTestContext::new_rust(
12611        lsp::ServerCapabilities {
12612            signature_help_provider: Some(lsp::SignatureHelpOptions {
12613                ..Default::default()
12614            }),
12615            ..Default::default()
12616        },
12617        cx,
12618    )
12619    .await;
12620
12621    let language = Language::new(
12622        LanguageConfig {
12623            name: "Rust".into(),
12624            brackets: BracketPairConfig {
12625                pairs: vec![
12626                    BracketPair {
12627                        start: "{".to_string(),
12628                        end: "}".to_string(),
12629                        close: true,
12630                        surround: true,
12631                        newline: true,
12632                    },
12633                    BracketPair {
12634                        start: "(".to_string(),
12635                        end: ")".to_string(),
12636                        close: true,
12637                        surround: true,
12638                        newline: true,
12639                    },
12640                    BracketPair {
12641                        start: "/*".to_string(),
12642                        end: " */".to_string(),
12643                        close: true,
12644                        surround: true,
12645                        newline: true,
12646                    },
12647                    BracketPair {
12648                        start: "[".to_string(),
12649                        end: "]".to_string(),
12650                        close: false,
12651                        surround: false,
12652                        newline: true,
12653                    },
12654                    BracketPair {
12655                        start: "\"".to_string(),
12656                        end: "\"".to_string(),
12657                        close: true,
12658                        surround: true,
12659                        newline: false,
12660                    },
12661                    BracketPair {
12662                        start: "<".to_string(),
12663                        end: ">".to_string(),
12664                        close: false,
12665                        surround: true,
12666                        newline: true,
12667                    },
12668                ],
12669                ..Default::default()
12670            },
12671            autoclose_before: "})]".to_string(),
12672            ..Default::default()
12673        },
12674        Some(tree_sitter_rust::LANGUAGE.into()),
12675    );
12676    let language = Arc::new(language);
12677
12678    cx.language_registry().add(language.clone());
12679    cx.update_buffer(|buffer, cx| {
12680        buffer.set_language(Some(language), cx);
12681    });
12682
12683    // Ensure that signature_help is not called when no signature help is enabled.
12684    cx.set_state(
12685        &r#"
12686            fn main() {
12687                sampleˇ
12688            }
12689        "#
12690        .unindent(),
12691    );
12692    cx.update_editor(|editor, window, cx| {
12693        editor.handle_input("(", window, cx);
12694    });
12695    cx.assert_editor_state(
12696        &"
12697            fn main() {
12698                sample(ˇ)
12699            }
12700        "
12701        .unindent(),
12702    );
12703    cx.editor(|editor, _, _| {
12704        assert!(editor.signature_help_state.task().is_none());
12705    });
12706
12707    let mocked_response = lsp::SignatureHelp {
12708        signatures: vec![lsp::SignatureInformation {
12709            label: "fn sample(param1: u8, param2: u8)".to_string(),
12710            documentation: None,
12711            parameters: Some(vec![
12712                lsp::ParameterInformation {
12713                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12714                    documentation: None,
12715                },
12716                lsp::ParameterInformation {
12717                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12718                    documentation: None,
12719                },
12720            ]),
12721            active_parameter: None,
12722        }],
12723        active_signature: Some(0),
12724        active_parameter: Some(0),
12725    };
12726
12727    // Ensure that signature_help is called when enabled afte edits
12728    cx.update(|_, cx| {
12729        cx.update_global::<SettingsStore, _>(|settings, cx| {
12730            settings.update_user_settings::<EditorSettings>(cx, |settings| {
12731                settings.auto_signature_help = Some(false);
12732                settings.show_signature_help_after_edits = Some(true);
12733            });
12734        });
12735    });
12736    cx.set_state(
12737        &r#"
12738            fn main() {
12739                sampleˇ
12740            }
12741        "#
12742        .unindent(),
12743    );
12744    cx.update_editor(|editor, window, cx| {
12745        editor.handle_input("(", window, cx);
12746    });
12747    cx.assert_editor_state(
12748        &"
12749            fn main() {
12750                sample(ˇ)
12751            }
12752        "
12753        .unindent(),
12754    );
12755    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12756    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12757        .await;
12758    cx.update_editor(|editor, _, _| {
12759        let signature_help_state = editor.signature_help_state.popover().cloned();
12760        assert!(signature_help_state.is_some());
12761        let signature = signature_help_state.unwrap();
12762        assert_eq!(
12763            signature.signatures[signature.current_signature].label,
12764            "fn sample(param1: u8, param2: u8)"
12765        );
12766        editor.signature_help_state = SignatureHelpState::default();
12767    });
12768
12769    // Ensure that signature_help is called when auto signature help override is enabled
12770    cx.update(|_, cx| {
12771        cx.update_global::<SettingsStore, _>(|settings, cx| {
12772            settings.update_user_settings::<EditorSettings>(cx, |settings| {
12773                settings.auto_signature_help = Some(true);
12774                settings.show_signature_help_after_edits = Some(false);
12775            });
12776        });
12777    });
12778    cx.set_state(
12779        &r#"
12780            fn main() {
12781                sampleˇ
12782            }
12783        "#
12784        .unindent(),
12785    );
12786    cx.update_editor(|editor, window, cx| {
12787        editor.handle_input("(", window, cx);
12788    });
12789    cx.assert_editor_state(
12790        &"
12791            fn main() {
12792                sample(ˇ)
12793            }
12794        "
12795        .unindent(),
12796    );
12797    handle_signature_help_request(&mut cx, mocked_response).await;
12798    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12799        .await;
12800    cx.editor(|editor, _, _| {
12801        let signature_help_state = editor.signature_help_state.popover().cloned();
12802        assert!(signature_help_state.is_some());
12803        let signature = signature_help_state.unwrap();
12804        assert_eq!(
12805            signature.signatures[signature.current_signature].label,
12806            "fn sample(param1: u8, param2: u8)"
12807        );
12808    });
12809}
12810
12811#[gpui::test]
12812async fn test_signature_help(cx: &mut TestAppContext) {
12813    init_test(cx, |_| {});
12814    cx.update(|cx| {
12815        cx.update_global::<SettingsStore, _>(|settings, cx| {
12816            settings.update_user_settings::<EditorSettings>(cx, |settings| {
12817                settings.auto_signature_help = Some(true);
12818            });
12819        });
12820    });
12821
12822    let mut cx = EditorLspTestContext::new_rust(
12823        lsp::ServerCapabilities {
12824            signature_help_provider: Some(lsp::SignatureHelpOptions {
12825                ..Default::default()
12826            }),
12827            ..Default::default()
12828        },
12829        cx,
12830    )
12831    .await;
12832
12833    // A test that directly calls `show_signature_help`
12834    cx.update_editor(|editor, window, cx| {
12835        editor.show_signature_help(&ShowSignatureHelp, window, cx);
12836    });
12837
12838    let mocked_response = lsp::SignatureHelp {
12839        signatures: vec![lsp::SignatureInformation {
12840            label: "fn sample(param1: u8, param2: u8)".to_string(),
12841            documentation: None,
12842            parameters: Some(vec![
12843                lsp::ParameterInformation {
12844                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12845                    documentation: None,
12846                },
12847                lsp::ParameterInformation {
12848                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12849                    documentation: None,
12850                },
12851            ]),
12852            active_parameter: None,
12853        }],
12854        active_signature: Some(0),
12855        active_parameter: Some(0),
12856    };
12857    handle_signature_help_request(&mut cx, mocked_response).await;
12858
12859    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12860        .await;
12861
12862    cx.editor(|editor, _, _| {
12863        let signature_help_state = editor.signature_help_state.popover().cloned();
12864        assert!(signature_help_state.is_some());
12865        let signature = signature_help_state.unwrap();
12866        assert_eq!(
12867            signature.signatures[signature.current_signature].label,
12868            "fn sample(param1: u8, param2: u8)"
12869        );
12870    });
12871
12872    // When exiting outside from inside the brackets, `signature_help` is closed.
12873    cx.set_state(indoc! {"
12874        fn main() {
12875            sample(ˇ);
12876        }
12877
12878        fn sample(param1: u8, param2: u8) {}
12879    "});
12880
12881    cx.update_editor(|editor, window, cx| {
12882        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12883            s.select_ranges([0..0])
12884        });
12885    });
12886
12887    let mocked_response = lsp::SignatureHelp {
12888        signatures: Vec::new(),
12889        active_signature: None,
12890        active_parameter: None,
12891    };
12892    handle_signature_help_request(&mut cx, mocked_response).await;
12893
12894    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
12895        .await;
12896
12897    cx.editor(|editor, _, _| {
12898        assert!(!editor.signature_help_state.is_shown());
12899    });
12900
12901    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
12902    cx.set_state(indoc! {"
12903        fn main() {
12904            sample(ˇ);
12905        }
12906
12907        fn sample(param1: u8, param2: u8) {}
12908    "});
12909
12910    let mocked_response = lsp::SignatureHelp {
12911        signatures: vec![lsp::SignatureInformation {
12912            label: "fn sample(param1: u8, param2: u8)".to_string(),
12913            documentation: None,
12914            parameters: Some(vec![
12915                lsp::ParameterInformation {
12916                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12917                    documentation: None,
12918                },
12919                lsp::ParameterInformation {
12920                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12921                    documentation: None,
12922                },
12923            ]),
12924            active_parameter: None,
12925        }],
12926        active_signature: Some(0),
12927        active_parameter: Some(0),
12928    };
12929    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12930    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12931        .await;
12932    cx.editor(|editor, _, _| {
12933        assert!(editor.signature_help_state.is_shown());
12934    });
12935
12936    // Restore the popover with more parameter input
12937    cx.set_state(indoc! {"
12938        fn main() {
12939            sample(param1, param2ˇ);
12940        }
12941
12942        fn sample(param1: u8, param2: u8) {}
12943    "});
12944
12945    let mocked_response = lsp::SignatureHelp {
12946        signatures: vec![lsp::SignatureInformation {
12947            label: "fn sample(param1: u8, param2: u8)".to_string(),
12948            documentation: None,
12949            parameters: Some(vec![
12950                lsp::ParameterInformation {
12951                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12952                    documentation: None,
12953                },
12954                lsp::ParameterInformation {
12955                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12956                    documentation: None,
12957                },
12958            ]),
12959            active_parameter: None,
12960        }],
12961        active_signature: Some(0),
12962        active_parameter: Some(1),
12963    };
12964    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12965    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12966        .await;
12967
12968    // When selecting a range, the popover is gone.
12969    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
12970    cx.update_editor(|editor, window, cx| {
12971        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12972            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
12973        })
12974    });
12975    cx.assert_editor_state(indoc! {"
12976        fn main() {
12977            sample(param1, «ˇparam2»);
12978        }
12979
12980        fn sample(param1: u8, param2: u8) {}
12981    "});
12982    cx.editor(|editor, _, _| {
12983        assert!(!editor.signature_help_state.is_shown());
12984    });
12985
12986    // When unselecting again, the popover is back if within the brackets.
12987    cx.update_editor(|editor, window, cx| {
12988        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12989            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
12990        })
12991    });
12992    cx.assert_editor_state(indoc! {"
12993        fn main() {
12994            sample(param1, ˇparam2);
12995        }
12996
12997        fn sample(param1: u8, param2: u8) {}
12998    "});
12999    handle_signature_help_request(&mut cx, mocked_response).await;
13000    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13001        .await;
13002    cx.editor(|editor, _, _| {
13003        assert!(editor.signature_help_state.is_shown());
13004    });
13005
13006    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
13007    cx.update_editor(|editor, window, cx| {
13008        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13009            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
13010            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13011        })
13012    });
13013    cx.assert_editor_state(indoc! {"
13014        fn main() {
13015            sample(param1, ˇparam2);
13016        }
13017
13018        fn sample(param1: u8, param2: u8) {}
13019    "});
13020
13021    let mocked_response = lsp::SignatureHelp {
13022        signatures: vec![lsp::SignatureInformation {
13023            label: "fn sample(param1: u8, param2: u8)".to_string(),
13024            documentation: None,
13025            parameters: Some(vec![
13026                lsp::ParameterInformation {
13027                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13028                    documentation: None,
13029                },
13030                lsp::ParameterInformation {
13031                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13032                    documentation: None,
13033                },
13034            ]),
13035            active_parameter: None,
13036        }],
13037        active_signature: Some(0),
13038        active_parameter: Some(1),
13039    };
13040    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13041    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13042        .await;
13043    cx.update_editor(|editor, _, cx| {
13044        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
13045    });
13046    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13047        .await;
13048    cx.update_editor(|editor, window, cx| {
13049        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13050            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13051        })
13052    });
13053    cx.assert_editor_state(indoc! {"
13054        fn main() {
13055            sample(param1, «ˇparam2»);
13056        }
13057
13058        fn sample(param1: u8, param2: u8) {}
13059    "});
13060    cx.update_editor(|editor, window, cx| {
13061        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13062            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13063        })
13064    });
13065    cx.assert_editor_state(indoc! {"
13066        fn main() {
13067            sample(param1, ˇparam2);
13068        }
13069
13070        fn sample(param1: u8, param2: u8) {}
13071    "});
13072    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
13073        .await;
13074}
13075
13076#[gpui::test]
13077async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
13078    init_test(cx, |_| {});
13079
13080    let mut cx = EditorLspTestContext::new_rust(
13081        lsp::ServerCapabilities {
13082            signature_help_provider: Some(lsp::SignatureHelpOptions {
13083                ..Default::default()
13084            }),
13085            ..Default::default()
13086        },
13087        cx,
13088    )
13089    .await;
13090
13091    cx.set_state(indoc! {"
13092        fn main() {
13093            overloadedˇ
13094        }
13095    "});
13096
13097    cx.update_editor(|editor, window, cx| {
13098        editor.handle_input("(", window, cx);
13099        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13100    });
13101
13102    // Mock response with 3 signatures
13103    let mocked_response = lsp::SignatureHelp {
13104        signatures: vec![
13105            lsp::SignatureInformation {
13106                label: "fn overloaded(x: i32)".to_string(),
13107                documentation: None,
13108                parameters: Some(vec![lsp::ParameterInformation {
13109                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13110                    documentation: None,
13111                }]),
13112                active_parameter: None,
13113            },
13114            lsp::SignatureInformation {
13115                label: "fn overloaded(x: i32, y: i32)".to_string(),
13116                documentation: None,
13117                parameters: Some(vec![
13118                    lsp::ParameterInformation {
13119                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13120                        documentation: None,
13121                    },
13122                    lsp::ParameterInformation {
13123                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13124                        documentation: None,
13125                    },
13126                ]),
13127                active_parameter: None,
13128            },
13129            lsp::SignatureInformation {
13130                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
13131                documentation: None,
13132                parameters: Some(vec![
13133                    lsp::ParameterInformation {
13134                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13135                        documentation: None,
13136                    },
13137                    lsp::ParameterInformation {
13138                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13139                        documentation: None,
13140                    },
13141                    lsp::ParameterInformation {
13142                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
13143                        documentation: None,
13144                    },
13145                ]),
13146                active_parameter: None,
13147            },
13148        ],
13149        active_signature: Some(1),
13150        active_parameter: Some(0),
13151    };
13152    handle_signature_help_request(&mut cx, mocked_response).await;
13153
13154    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13155        .await;
13156
13157    // Verify we have multiple signatures and the right one is selected
13158    cx.editor(|editor, _, _| {
13159        let popover = editor.signature_help_state.popover().cloned().unwrap();
13160        assert_eq!(popover.signatures.len(), 3);
13161        // active_signature was 1, so that should be the current
13162        assert_eq!(popover.current_signature, 1);
13163        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
13164        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
13165        assert_eq!(
13166            popover.signatures[2].label,
13167            "fn overloaded(x: i32, y: i32, z: i32)"
13168        );
13169    });
13170
13171    // Test navigation functionality
13172    cx.update_editor(|editor, window, cx| {
13173        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13174    });
13175
13176    cx.editor(|editor, _, _| {
13177        let popover = editor.signature_help_state.popover().cloned().unwrap();
13178        assert_eq!(popover.current_signature, 2);
13179    });
13180
13181    // Test wrap around
13182    cx.update_editor(|editor, window, cx| {
13183        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13184    });
13185
13186    cx.editor(|editor, _, _| {
13187        let popover = editor.signature_help_state.popover().cloned().unwrap();
13188        assert_eq!(popover.current_signature, 0);
13189    });
13190
13191    // Test previous navigation
13192    cx.update_editor(|editor, window, cx| {
13193        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
13194    });
13195
13196    cx.editor(|editor, _, _| {
13197        let popover = editor.signature_help_state.popover().cloned().unwrap();
13198        assert_eq!(popover.current_signature, 2);
13199    });
13200}
13201
13202#[gpui::test]
13203async fn test_completion_mode(cx: &mut TestAppContext) {
13204    init_test(cx, |_| {});
13205    let mut cx = EditorLspTestContext::new_rust(
13206        lsp::ServerCapabilities {
13207            completion_provider: Some(lsp::CompletionOptions {
13208                resolve_provider: Some(true),
13209                ..Default::default()
13210            }),
13211            ..Default::default()
13212        },
13213        cx,
13214    )
13215    .await;
13216
13217    struct Run {
13218        run_description: &'static str,
13219        initial_state: String,
13220        buffer_marked_text: String,
13221        completion_label: &'static str,
13222        completion_text: &'static str,
13223        expected_with_insert_mode: String,
13224        expected_with_replace_mode: String,
13225        expected_with_replace_subsequence_mode: String,
13226        expected_with_replace_suffix_mode: String,
13227    }
13228
13229    let runs = [
13230        Run {
13231            run_description: "Start of word matches completion text",
13232            initial_state: "before ediˇ after".into(),
13233            buffer_marked_text: "before <edi|> after".into(),
13234            completion_label: "editor",
13235            completion_text: "editor",
13236            expected_with_insert_mode: "before editorˇ after".into(),
13237            expected_with_replace_mode: "before editorˇ after".into(),
13238            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13239            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13240        },
13241        Run {
13242            run_description: "Accept same text at the middle of the word",
13243            initial_state: "before ediˇtor after".into(),
13244            buffer_marked_text: "before <edi|tor> after".into(),
13245            completion_label: "editor",
13246            completion_text: "editor",
13247            expected_with_insert_mode: "before editorˇtor after".into(),
13248            expected_with_replace_mode: "before editorˇ after".into(),
13249            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13250            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13251        },
13252        Run {
13253            run_description: "End of word matches completion text -- cursor at end",
13254            initial_state: "before torˇ after".into(),
13255            buffer_marked_text: "before <tor|> after".into(),
13256            completion_label: "editor",
13257            completion_text: "editor",
13258            expected_with_insert_mode: "before editorˇ after".into(),
13259            expected_with_replace_mode: "before editorˇ after".into(),
13260            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13261            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13262        },
13263        Run {
13264            run_description: "End of word matches completion text -- cursor at start",
13265            initial_state: "before ˇtor after".into(),
13266            buffer_marked_text: "before <|tor> after".into(),
13267            completion_label: "editor",
13268            completion_text: "editor",
13269            expected_with_insert_mode: "before editorˇtor after".into(),
13270            expected_with_replace_mode: "before editorˇ after".into(),
13271            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13272            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13273        },
13274        Run {
13275            run_description: "Prepend text containing whitespace",
13276            initial_state: "pˇfield: bool".into(),
13277            buffer_marked_text: "<p|field>: bool".into(),
13278            completion_label: "pub ",
13279            completion_text: "pub ",
13280            expected_with_insert_mode: "pub ˇfield: bool".into(),
13281            expected_with_replace_mode: "pub ˇ: bool".into(),
13282            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
13283            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
13284        },
13285        Run {
13286            run_description: "Add element to start of list",
13287            initial_state: "[element_ˇelement_2]".into(),
13288            buffer_marked_text: "[<element_|element_2>]".into(),
13289            completion_label: "element_1",
13290            completion_text: "element_1",
13291            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
13292            expected_with_replace_mode: "[element_1ˇ]".into(),
13293            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
13294            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
13295        },
13296        Run {
13297            run_description: "Add element to start of list -- first and second elements are equal",
13298            initial_state: "[elˇelement]".into(),
13299            buffer_marked_text: "[<el|element>]".into(),
13300            completion_label: "element",
13301            completion_text: "element",
13302            expected_with_insert_mode: "[elementˇelement]".into(),
13303            expected_with_replace_mode: "[elementˇ]".into(),
13304            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
13305            expected_with_replace_suffix_mode: "[elementˇ]".into(),
13306        },
13307        Run {
13308            run_description: "Ends with matching suffix",
13309            initial_state: "SubˇError".into(),
13310            buffer_marked_text: "<Sub|Error>".into(),
13311            completion_label: "SubscriptionError",
13312            completion_text: "SubscriptionError",
13313            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
13314            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13315            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13316            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
13317        },
13318        Run {
13319            run_description: "Suffix is a subsequence -- contiguous",
13320            initial_state: "SubˇErr".into(),
13321            buffer_marked_text: "<Sub|Err>".into(),
13322            completion_label: "SubscriptionError",
13323            completion_text: "SubscriptionError",
13324            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
13325            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13326            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13327            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
13328        },
13329        Run {
13330            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
13331            initial_state: "Suˇscrirr".into(),
13332            buffer_marked_text: "<Su|scrirr>".into(),
13333            completion_label: "SubscriptionError",
13334            completion_text: "SubscriptionError",
13335            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
13336            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13337            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13338            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
13339        },
13340        Run {
13341            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
13342            initial_state: "foo(indˇix)".into(),
13343            buffer_marked_text: "foo(<ind|ix>)".into(),
13344            completion_label: "node_index",
13345            completion_text: "node_index",
13346            expected_with_insert_mode: "foo(node_indexˇix)".into(),
13347            expected_with_replace_mode: "foo(node_indexˇ)".into(),
13348            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
13349            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
13350        },
13351        Run {
13352            run_description: "Replace range ends before cursor - should extend to cursor",
13353            initial_state: "before editˇo after".into(),
13354            buffer_marked_text: "before <{ed}>it|o after".into(),
13355            completion_label: "editor",
13356            completion_text: "editor",
13357            expected_with_insert_mode: "before editorˇo after".into(),
13358            expected_with_replace_mode: "before editorˇo after".into(),
13359            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
13360            expected_with_replace_suffix_mode: "before editorˇo after".into(),
13361        },
13362        Run {
13363            run_description: "Uses label for suffix matching",
13364            initial_state: "before ediˇtor after".into(),
13365            buffer_marked_text: "before <edi|tor> after".into(),
13366            completion_label: "editor",
13367            completion_text: "editor()",
13368            expected_with_insert_mode: "before editor()ˇtor after".into(),
13369            expected_with_replace_mode: "before editor()ˇ after".into(),
13370            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
13371            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
13372        },
13373        Run {
13374            run_description: "Case insensitive subsequence and suffix matching",
13375            initial_state: "before EDiˇtoR after".into(),
13376            buffer_marked_text: "before <EDi|toR> after".into(),
13377            completion_label: "editor",
13378            completion_text: "editor",
13379            expected_with_insert_mode: "before editorˇtoR after".into(),
13380            expected_with_replace_mode: "before editorˇ after".into(),
13381            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13382            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13383        },
13384    ];
13385
13386    for run in runs {
13387        let run_variations = [
13388            (LspInsertMode::Insert, run.expected_with_insert_mode),
13389            (LspInsertMode::Replace, run.expected_with_replace_mode),
13390            (
13391                LspInsertMode::ReplaceSubsequence,
13392                run.expected_with_replace_subsequence_mode,
13393            ),
13394            (
13395                LspInsertMode::ReplaceSuffix,
13396                run.expected_with_replace_suffix_mode,
13397            ),
13398        ];
13399
13400        for (lsp_insert_mode, expected_text) in run_variations {
13401            eprintln!(
13402                "run = {:?}, mode = {lsp_insert_mode:.?}",
13403                run.run_description,
13404            );
13405
13406            update_test_language_settings(&mut cx, |settings| {
13407                settings.defaults.completions = Some(CompletionSettings {
13408                    lsp_insert_mode,
13409                    words: WordsCompletionMode::Disabled,
13410                    words_min_length: 0,
13411                    lsp: true,
13412                    lsp_fetch_timeout_ms: 0,
13413                });
13414            });
13415
13416            cx.set_state(&run.initial_state);
13417            cx.update_editor(|editor, window, cx| {
13418                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13419            });
13420
13421            let counter = Arc::new(AtomicUsize::new(0));
13422            handle_completion_request_with_insert_and_replace(
13423                &mut cx,
13424                &run.buffer_marked_text,
13425                vec![(run.completion_label, run.completion_text)],
13426                counter.clone(),
13427            )
13428            .await;
13429            cx.condition(|editor, _| editor.context_menu_visible())
13430                .await;
13431            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13432
13433            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13434                editor
13435                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
13436                    .unwrap()
13437            });
13438            cx.assert_editor_state(&expected_text);
13439            handle_resolve_completion_request(&mut cx, None).await;
13440            apply_additional_edits.await.unwrap();
13441        }
13442    }
13443}
13444
13445#[gpui::test]
13446async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
13447    init_test(cx, |_| {});
13448    let mut cx = EditorLspTestContext::new_rust(
13449        lsp::ServerCapabilities {
13450            completion_provider: Some(lsp::CompletionOptions {
13451                resolve_provider: Some(true),
13452                ..Default::default()
13453            }),
13454            ..Default::default()
13455        },
13456        cx,
13457    )
13458    .await;
13459
13460    let initial_state = "SubˇError";
13461    let buffer_marked_text = "<Sub|Error>";
13462    let completion_text = "SubscriptionError";
13463    let expected_with_insert_mode = "SubscriptionErrorˇError";
13464    let expected_with_replace_mode = "SubscriptionErrorˇ";
13465
13466    update_test_language_settings(&mut cx, |settings| {
13467        settings.defaults.completions = Some(CompletionSettings {
13468            words: WordsCompletionMode::Disabled,
13469            words_min_length: 0,
13470            // set the opposite here to ensure that the action is overriding the default behavior
13471            lsp_insert_mode: LspInsertMode::Insert,
13472            lsp: true,
13473            lsp_fetch_timeout_ms: 0,
13474        });
13475    });
13476
13477    cx.set_state(initial_state);
13478    cx.update_editor(|editor, window, cx| {
13479        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13480    });
13481
13482    let counter = Arc::new(AtomicUsize::new(0));
13483    handle_completion_request_with_insert_and_replace(
13484        &mut cx,
13485        buffer_marked_text,
13486        vec![(completion_text, completion_text)],
13487        counter.clone(),
13488    )
13489    .await;
13490    cx.condition(|editor, _| editor.context_menu_visible())
13491        .await;
13492    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13493
13494    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13495        editor
13496            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13497            .unwrap()
13498    });
13499    cx.assert_editor_state(expected_with_replace_mode);
13500    handle_resolve_completion_request(&mut cx, None).await;
13501    apply_additional_edits.await.unwrap();
13502
13503    update_test_language_settings(&mut cx, |settings| {
13504        settings.defaults.completions = Some(CompletionSettings {
13505            words: WordsCompletionMode::Disabled,
13506            words_min_length: 0,
13507            // set the opposite here to ensure that the action is overriding the default behavior
13508            lsp_insert_mode: LspInsertMode::Replace,
13509            lsp: true,
13510            lsp_fetch_timeout_ms: 0,
13511        });
13512    });
13513
13514    cx.set_state(initial_state);
13515    cx.update_editor(|editor, window, cx| {
13516        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13517    });
13518    handle_completion_request_with_insert_and_replace(
13519        &mut cx,
13520        buffer_marked_text,
13521        vec![(completion_text, completion_text)],
13522        counter.clone(),
13523    )
13524    .await;
13525    cx.condition(|editor, _| editor.context_menu_visible())
13526        .await;
13527    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
13528
13529    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13530        editor
13531            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
13532            .unwrap()
13533    });
13534    cx.assert_editor_state(expected_with_insert_mode);
13535    handle_resolve_completion_request(&mut cx, None).await;
13536    apply_additional_edits.await.unwrap();
13537}
13538
13539#[gpui::test]
13540async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
13541    init_test(cx, |_| {});
13542    let mut cx = EditorLspTestContext::new_rust(
13543        lsp::ServerCapabilities {
13544            completion_provider: Some(lsp::CompletionOptions {
13545                resolve_provider: Some(true),
13546                ..Default::default()
13547            }),
13548            ..Default::default()
13549        },
13550        cx,
13551    )
13552    .await;
13553
13554    // scenario: surrounding text matches completion text
13555    let completion_text = "to_offset";
13556    let initial_state = indoc! {"
13557        1. buf.to_offˇsuffix
13558        2. buf.to_offˇsuf
13559        3. buf.to_offˇfix
13560        4. buf.to_offˇ
13561        5. into_offˇensive
13562        6. ˇsuffix
13563        7. let ˇ //
13564        8. aaˇzz
13565        9. buf.to_off«zzzzzˇ»suffix
13566        10. buf.«ˇzzzzz»suffix
13567        11. to_off«ˇzzzzz»
13568
13569        buf.to_offˇsuffix  // newest cursor
13570    "};
13571    let completion_marked_buffer = indoc! {"
13572        1. buf.to_offsuffix
13573        2. buf.to_offsuf
13574        3. buf.to_offfix
13575        4. buf.to_off
13576        5. into_offensive
13577        6. suffix
13578        7. let  //
13579        8. aazz
13580        9. buf.to_offzzzzzsuffix
13581        10. buf.zzzzzsuffix
13582        11. to_offzzzzz
13583
13584        buf.<to_off|suffix>  // newest cursor
13585    "};
13586    let expected = indoc! {"
13587        1. buf.to_offsetˇ
13588        2. buf.to_offsetˇsuf
13589        3. buf.to_offsetˇfix
13590        4. buf.to_offsetˇ
13591        5. into_offsetˇensive
13592        6. to_offsetˇsuffix
13593        7. let to_offsetˇ //
13594        8. aato_offsetˇzz
13595        9. buf.to_offsetˇ
13596        10. buf.to_offsetˇsuffix
13597        11. to_offsetˇ
13598
13599        buf.to_offsetˇ  // newest cursor
13600    "};
13601    cx.set_state(initial_state);
13602    cx.update_editor(|editor, window, cx| {
13603        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13604    });
13605    handle_completion_request_with_insert_and_replace(
13606        &mut cx,
13607        completion_marked_buffer,
13608        vec![(completion_text, completion_text)],
13609        Arc::new(AtomicUsize::new(0)),
13610    )
13611    .await;
13612    cx.condition(|editor, _| editor.context_menu_visible())
13613        .await;
13614    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13615        editor
13616            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13617            .unwrap()
13618    });
13619    cx.assert_editor_state(expected);
13620    handle_resolve_completion_request(&mut cx, None).await;
13621    apply_additional_edits.await.unwrap();
13622
13623    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
13624    let completion_text = "foo_and_bar";
13625    let initial_state = indoc! {"
13626        1. ooanbˇ
13627        2. zooanbˇ
13628        3. ooanbˇz
13629        4. zooanbˇz
13630        5. ooanˇ
13631        6. oanbˇ
13632
13633        ooanbˇ
13634    "};
13635    let completion_marked_buffer = indoc! {"
13636        1. ooanb
13637        2. zooanb
13638        3. ooanbz
13639        4. zooanbz
13640        5. ooan
13641        6. oanb
13642
13643        <ooanb|>
13644    "};
13645    let expected = indoc! {"
13646        1. foo_and_barˇ
13647        2. zfoo_and_barˇ
13648        3. foo_and_barˇz
13649        4. zfoo_and_barˇz
13650        5. ooanfoo_and_barˇ
13651        6. oanbfoo_and_barˇ
13652
13653        foo_and_barˇ
13654    "};
13655    cx.set_state(initial_state);
13656    cx.update_editor(|editor, window, cx| {
13657        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13658    });
13659    handle_completion_request_with_insert_and_replace(
13660        &mut cx,
13661        completion_marked_buffer,
13662        vec![(completion_text, completion_text)],
13663        Arc::new(AtomicUsize::new(0)),
13664    )
13665    .await;
13666    cx.condition(|editor, _| editor.context_menu_visible())
13667        .await;
13668    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13669        editor
13670            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13671            .unwrap()
13672    });
13673    cx.assert_editor_state(expected);
13674    handle_resolve_completion_request(&mut cx, None).await;
13675    apply_additional_edits.await.unwrap();
13676
13677    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
13678    // (expects the same as if it was inserted at the end)
13679    let completion_text = "foo_and_bar";
13680    let initial_state = indoc! {"
13681        1. ooˇanb
13682        2. zooˇanb
13683        3. ooˇanbz
13684        4. zooˇanbz
13685
13686        ooˇanb
13687    "};
13688    let completion_marked_buffer = indoc! {"
13689        1. ooanb
13690        2. zooanb
13691        3. ooanbz
13692        4. zooanbz
13693
13694        <oo|anb>
13695    "};
13696    let expected = indoc! {"
13697        1. foo_and_barˇ
13698        2. zfoo_and_barˇ
13699        3. foo_and_barˇz
13700        4. zfoo_and_barˇz
13701
13702        foo_and_barˇ
13703    "};
13704    cx.set_state(initial_state);
13705    cx.update_editor(|editor, window, cx| {
13706        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13707    });
13708    handle_completion_request_with_insert_and_replace(
13709        &mut cx,
13710        completion_marked_buffer,
13711        vec![(completion_text, completion_text)],
13712        Arc::new(AtomicUsize::new(0)),
13713    )
13714    .await;
13715    cx.condition(|editor, _| editor.context_menu_visible())
13716        .await;
13717    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13718        editor
13719            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13720            .unwrap()
13721    });
13722    cx.assert_editor_state(expected);
13723    handle_resolve_completion_request(&mut cx, None).await;
13724    apply_additional_edits.await.unwrap();
13725}
13726
13727// This used to crash
13728#[gpui::test]
13729async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
13730    init_test(cx, |_| {});
13731
13732    let buffer_text = indoc! {"
13733        fn main() {
13734            10.satu;
13735
13736            //
13737            // separate cursors so they open in different excerpts (manually reproducible)
13738            //
13739
13740            10.satu20;
13741        }
13742    "};
13743    let multibuffer_text_with_selections = indoc! {"
13744        fn main() {
13745            10.satuˇ;
13746
13747            //
13748
13749            //
13750
13751            10.satuˇ20;
13752        }
13753    "};
13754    let expected_multibuffer = indoc! {"
13755        fn main() {
13756            10.saturating_sub()ˇ;
13757
13758            //
13759
13760            //
13761
13762            10.saturating_sub()ˇ;
13763        }
13764    "};
13765
13766    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
13767    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
13768
13769    let fs = FakeFs::new(cx.executor());
13770    fs.insert_tree(
13771        path!("/a"),
13772        json!({
13773            "main.rs": buffer_text,
13774        }),
13775    )
13776    .await;
13777
13778    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13779    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13780    language_registry.add(rust_lang());
13781    let mut fake_servers = language_registry.register_fake_lsp(
13782        "Rust",
13783        FakeLspAdapter {
13784            capabilities: lsp::ServerCapabilities {
13785                completion_provider: Some(lsp::CompletionOptions {
13786                    resolve_provider: None,
13787                    ..lsp::CompletionOptions::default()
13788                }),
13789                ..lsp::ServerCapabilities::default()
13790            },
13791            ..FakeLspAdapter::default()
13792        },
13793    );
13794    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13795    let cx = &mut VisualTestContext::from_window(*workspace, cx);
13796    let buffer = project
13797        .update(cx, |project, cx| {
13798            project.open_local_buffer(path!("/a/main.rs"), cx)
13799        })
13800        .await
13801        .unwrap();
13802
13803    let multi_buffer = cx.new(|cx| {
13804        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
13805        multi_buffer.push_excerpts(
13806            buffer.clone(),
13807            [ExcerptRange::new(0..first_excerpt_end)],
13808            cx,
13809        );
13810        multi_buffer.push_excerpts(
13811            buffer.clone(),
13812            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
13813            cx,
13814        );
13815        multi_buffer
13816    });
13817
13818    let editor = workspace
13819        .update(cx, |_, window, cx| {
13820            cx.new(|cx| {
13821                Editor::new(
13822                    EditorMode::Full {
13823                        scale_ui_elements_with_buffer_font_size: false,
13824                        show_active_line_background: false,
13825                        sized_by_content: false,
13826                    },
13827                    multi_buffer.clone(),
13828                    Some(project.clone()),
13829                    window,
13830                    cx,
13831                )
13832            })
13833        })
13834        .unwrap();
13835
13836    let pane = workspace
13837        .update(cx, |workspace, _, _| workspace.active_pane().clone())
13838        .unwrap();
13839    pane.update_in(cx, |pane, window, cx| {
13840        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
13841    });
13842
13843    let fake_server = fake_servers.next().await.unwrap();
13844
13845    editor.update_in(cx, |editor, window, cx| {
13846        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13847            s.select_ranges([
13848                Point::new(1, 11)..Point::new(1, 11),
13849                Point::new(7, 11)..Point::new(7, 11),
13850            ])
13851        });
13852
13853        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
13854    });
13855
13856    editor.update_in(cx, |editor, window, cx| {
13857        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13858    });
13859
13860    fake_server
13861        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13862            let completion_item = lsp::CompletionItem {
13863                label: "saturating_sub()".into(),
13864                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
13865                    lsp::InsertReplaceEdit {
13866                        new_text: "saturating_sub()".to_owned(),
13867                        insert: lsp::Range::new(
13868                            lsp::Position::new(7, 7),
13869                            lsp::Position::new(7, 11),
13870                        ),
13871                        replace: lsp::Range::new(
13872                            lsp::Position::new(7, 7),
13873                            lsp::Position::new(7, 13),
13874                        ),
13875                    },
13876                )),
13877                ..lsp::CompletionItem::default()
13878            };
13879
13880            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
13881        })
13882        .next()
13883        .await
13884        .unwrap();
13885
13886    cx.condition(&editor, |editor, _| editor.context_menu_visible())
13887        .await;
13888
13889    editor
13890        .update_in(cx, |editor, window, cx| {
13891            editor
13892                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13893                .unwrap()
13894        })
13895        .await
13896        .unwrap();
13897
13898    editor.update(cx, |editor, cx| {
13899        assert_text_with_selections(editor, expected_multibuffer, cx);
13900    })
13901}
13902
13903#[gpui::test]
13904async fn test_completion(cx: &mut TestAppContext) {
13905    init_test(cx, |_| {});
13906
13907    let mut cx = EditorLspTestContext::new_rust(
13908        lsp::ServerCapabilities {
13909            completion_provider: Some(lsp::CompletionOptions {
13910                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
13911                resolve_provider: Some(true),
13912                ..Default::default()
13913            }),
13914            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13915            ..Default::default()
13916        },
13917        cx,
13918    )
13919    .await;
13920    let counter = Arc::new(AtomicUsize::new(0));
13921
13922    cx.set_state(indoc! {"
13923        oneˇ
13924        two
13925        three
13926    "});
13927    cx.simulate_keystroke(".");
13928    handle_completion_request(
13929        indoc! {"
13930            one.|<>
13931            two
13932            three
13933        "},
13934        vec!["first_completion", "second_completion"],
13935        true,
13936        counter.clone(),
13937        &mut cx,
13938    )
13939    .await;
13940    cx.condition(|editor, _| editor.context_menu_visible())
13941        .await;
13942    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13943
13944    let _handler = handle_signature_help_request(
13945        &mut cx,
13946        lsp::SignatureHelp {
13947            signatures: vec![lsp::SignatureInformation {
13948                label: "test signature".to_string(),
13949                documentation: None,
13950                parameters: Some(vec![lsp::ParameterInformation {
13951                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
13952                    documentation: None,
13953                }]),
13954                active_parameter: None,
13955            }],
13956            active_signature: None,
13957            active_parameter: None,
13958        },
13959    );
13960    cx.update_editor(|editor, window, cx| {
13961        assert!(
13962            !editor.signature_help_state.is_shown(),
13963            "No signature help was called for"
13964        );
13965        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13966    });
13967    cx.run_until_parked();
13968    cx.update_editor(|editor, _, _| {
13969        assert!(
13970            !editor.signature_help_state.is_shown(),
13971            "No signature help should be shown when completions menu is open"
13972        );
13973    });
13974
13975    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13976        editor.context_menu_next(&Default::default(), window, cx);
13977        editor
13978            .confirm_completion(&ConfirmCompletion::default(), window, cx)
13979            .unwrap()
13980    });
13981    cx.assert_editor_state(indoc! {"
13982        one.second_completionˇ
13983        two
13984        three
13985    "});
13986
13987    handle_resolve_completion_request(
13988        &mut cx,
13989        Some(vec![
13990            (
13991                //This overlaps with the primary completion edit which is
13992                //misbehavior from the LSP spec, test that we filter it out
13993                indoc! {"
13994                    one.second_ˇcompletion
13995                    two
13996                    threeˇ
13997                "},
13998                "overlapping additional edit",
13999            ),
14000            (
14001                indoc! {"
14002                    one.second_completion
14003                    two
14004                    threeˇ
14005                "},
14006                "\nadditional edit",
14007            ),
14008        ]),
14009    )
14010    .await;
14011    apply_additional_edits.await.unwrap();
14012    cx.assert_editor_state(indoc! {"
14013        one.second_completionˇ
14014        two
14015        three
14016        additional edit
14017    "});
14018
14019    cx.set_state(indoc! {"
14020        one.second_completion
14021        twoˇ
14022        threeˇ
14023        additional edit
14024    "});
14025    cx.simulate_keystroke(" ");
14026    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14027    cx.simulate_keystroke("s");
14028    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14029
14030    cx.assert_editor_state(indoc! {"
14031        one.second_completion
14032        two sˇ
14033        three sˇ
14034        additional edit
14035    "});
14036    handle_completion_request(
14037        indoc! {"
14038            one.second_completion
14039            two s
14040            three <s|>
14041            additional edit
14042        "},
14043        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14044        true,
14045        counter.clone(),
14046        &mut cx,
14047    )
14048    .await;
14049    cx.condition(|editor, _| editor.context_menu_visible())
14050        .await;
14051    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14052
14053    cx.simulate_keystroke("i");
14054
14055    handle_completion_request(
14056        indoc! {"
14057            one.second_completion
14058            two si
14059            three <si|>
14060            additional edit
14061        "},
14062        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14063        true,
14064        counter.clone(),
14065        &mut cx,
14066    )
14067    .await;
14068    cx.condition(|editor, _| editor.context_menu_visible())
14069        .await;
14070    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14071
14072    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14073        editor
14074            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14075            .unwrap()
14076    });
14077    cx.assert_editor_state(indoc! {"
14078        one.second_completion
14079        two sixth_completionˇ
14080        three sixth_completionˇ
14081        additional edit
14082    "});
14083
14084    apply_additional_edits.await.unwrap();
14085
14086    update_test_language_settings(&mut cx, |settings| {
14087        settings.defaults.show_completions_on_input = Some(false);
14088    });
14089    cx.set_state("editorˇ");
14090    cx.simulate_keystroke(".");
14091    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14092    cx.simulate_keystrokes("c l o");
14093    cx.assert_editor_state("editor.cloˇ");
14094    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14095    cx.update_editor(|editor, window, cx| {
14096        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14097    });
14098    handle_completion_request(
14099        "editor.<clo|>",
14100        vec!["close", "clobber"],
14101        true,
14102        counter.clone(),
14103        &mut cx,
14104    )
14105    .await;
14106    cx.condition(|editor, _| editor.context_menu_visible())
14107        .await;
14108    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
14109
14110    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14111        editor
14112            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14113            .unwrap()
14114    });
14115    cx.assert_editor_state("editor.clobberˇ");
14116    handle_resolve_completion_request(&mut cx, None).await;
14117    apply_additional_edits.await.unwrap();
14118}
14119
14120#[gpui::test]
14121async fn test_completion_reuse(cx: &mut TestAppContext) {
14122    init_test(cx, |_| {});
14123
14124    let mut cx = EditorLspTestContext::new_rust(
14125        lsp::ServerCapabilities {
14126            completion_provider: Some(lsp::CompletionOptions {
14127                trigger_characters: Some(vec![".".to_string()]),
14128                ..Default::default()
14129            }),
14130            ..Default::default()
14131        },
14132        cx,
14133    )
14134    .await;
14135
14136    let counter = Arc::new(AtomicUsize::new(0));
14137    cx.set_state("objˇ");
14138    cx.simulate_keystroke(".");
14139
14140    // Initial completion request returns complete results
14141    let is_incomplete = false;
14142    handle_completion_request(
14143        "obj.|<>",
14144        vec!["a", "ab", "abc"],
14145        is_incomplete,
14146        counter.clone(),
14147        &mut cx,
14148    )
14149    .await;
14150    cx.run_until_parked();
14151    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14152    cx.assert_editor_state("obj.ˇ");
14153    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14154
14155    // Type "a" - filters existing completions
14156    cx.simulate_keystroke("a");
14157    cx.run_until_parked();
14158    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14159    cx.assert_editor_state("obj.aˇ");
14160    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14161
14162    // Type "b" - filters existing completions
14163    cx.simulate_keystroke("b");
14164    cx.run_until_parked();
14165    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14166    cx.assert_editor_state("obj.abˇ");
14167    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14168
14169    // Type "c" - filters existing completions
14170    cx.simulate_keystroke("c");
14171    cx.run_until_parked();
14172    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14173    cx.assert_editor_state("obj.abcˇ");
14174    check_displayed_completions(vec!["abc"], &mut cx);
14175
14176    // Backspace to delete "c" - filters existing completions
14177    cx.update_editor(|editor, window, cx| {
14178        editor.backspace(&Backspace, window, cx);
14179    });
14180    cx.run_until_parked();
14181    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14182    cx.assert_editor_state("obj.abˇ");
14183    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14184
14185    // Moving cursor to the left dismisses menu.
14186    cx.update_editor(|editor, window, cx| {
14187        editor.move_left(&MoveLeft, window, cx);
14188    });
14189    cx.run_until_parked();
14190    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14191    cx.assert_editor_state("obj.aˇb");
14192    cx.update_editor(|editor, _, _| {
14193        assert_eq!(editor.context_menu_visible(), false);
14194    });
14195
14196    // Type "b" - new request
14197    cx.simulate_keystroke("b");
14198    let is_incomplete = false;
14199    handle_completion_request(
14200        "obj.<ab|>a",
14201        vec!["ab", "abc"],
14202        is_incomplete,
14203        counter.clone(),
14204        &mut cx,
14205    )
14206    .await;
14207    cx.run_until_parked();
14208    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14209    cx.assert_editor_state("obj.abˇb");
14210    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14211
14212    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
14213    cx.update_editor(|editor, window, cx| {
14214        editor.backspace(&Backspace, window, cx);
14215    });
14216    let is_incomplete = false;
14217    handle_completion_request(
14218        "obj.<a|>b",
14219        vec!["a", "ab", "abc"],
14220        is_incomplete,
14221        counter.clone(),
14222        &mut cx,
14223    )
14224    .await;
14225    cx.run_until_parked();
14226    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14227    cx.assert_editor_state("obj.aˇb");
14228    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14229
14230    // Backspace to delete "a" - dismisses menu.
14231    cx.update_editor(|editor, window, cx| {
14232        editor.backspace(&Backspace, window, cx);
14233    });
14234    cx.run_until_parked();
14235    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14236    cx.assert_editor_state("obj.ˇb");
14237    cx.update_editor(|editor, _, _| {
14238        assert_eq!(editor.context_menu_visible(), false);
14239    });
14240}
14241
14242#[gpui::test]
14243async fn test_word_completion(cx: &mut TestAppContext) {
14244    let lsp_fetch_timeout_ms = 10;
14245    init_test(cx, |language_settings| {
14246        language_settings.defaults.completions = Some(CompletionSettings {
14247            words: WordsCompletionMode::Fallback,
14248            words_min_length: 0,
14249            lsp: true,
14250            lsp_fetch_timeout_ms: 10,
14251            lsp_insert_mode: LspInsertMode::Insert,
14252        });
14253    });
14254
14255    let mut cx = EditorLspTestContext::new_rust(
14256        lsp::ServerCapabilities {
14257            completion_provider: Some(lsp::CompletionOptions {
14258                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14259                ..lsp::CompletionOptions::default()
14260            }),
14261            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14262            ..lsp::ServerCapabilities::default()
14263        },
14264        cx,
14265    )
14266    .await;
14267
14268    let throttle_completions = Arc::new(AtomicBool::new(false));
14269
14270    let lsp_throttle_completions = throttle_completions.clone();
14271    let _completion_requests_handler =
14272        cx.lsp
14273            .server
14274            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
14275                let lsp_throttle_completions = lsp_throttle_completions.clone();
14276                let cx = cx.clone();
14277                async move {
14278                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
14279                        cx.background_executor()
14280                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
14281                            .await;
14282                    }
14283                    Ok(Some(lsp::CompletionResponse::Array(vec![
14284                        lsp::CompletionItem {
14285                            label: "first".into(),
14286                            ..lsp::CompletionItem::default()
14287                        },
14288                        lsp::CompletionItem {
14289                            label: "last".into(),
14290                            ..lsp::CompletionItem::default()
14291                        },
14292                    ])))
14293                }
14294            });
14295
14296    cx.set_state(indoc! {"
14297        oneˇ
14298        two
14299        three
14300    "});
14301    cx.simulate_keystroke(".");
14302    cx.executor().run_until_parked();
14303    cx.condition(|editor, _| editor.context_menu_visible())
14304        .await;
14305    cx.update_editor(|editor, window, cx| {
14306        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14307        {
14308            assert_eq!(
14309                completion_menu_entries(menu),
14310                &["first", "last"],
14311                "When LSP server is fast to reply, no fallback word completions are used"
14312            );
14313        } else {
14314            panic!("expected completion menu to be open");
14315        }
14316        editor.cancel(&Cancel, window, cx);
14317    });
14318    cx.executor().run_until_parked();
14319    cx.condition(|editor, _| !editor.context_menu_visible())
14320        .await;
14321
14322    throttle_completions.store(true, atomic::Ordering::Release);
14323    cx.simulate_keystroke(".");
14324    cx.executor()
14325        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
14326    cx.executor().run_until_parked();
14327    cx.condition(|editor, _| editor.context_menu_visible())
14328        .await;
14329    cx.update_editor(|editor, _, _| {
14330        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14331        {
14332            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
14333                "When LSP server is slow, document words can be shown instead, if configured accordingly");
14334        } else {
14335            panic!("expected completion menu to be open");
14336        }
14337    });
14338}
14339
14340#[gpui::test]
14341async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
14342    init_test(cx, |language_settings| {
14343        language_settings.defaults.completions = Some(CompletionSettings {
14344            words: WordsCompletionMode::Enabled,
14345            words_min_length: 0,
14346            lsp: true,
14347            lsp_fetch_timeout_ms: 0,
14348            lsp_insert_mode: LspInsertMode::Insert,
14349        });
14350    });
14351
14352    let mut cx = EditorLspTestContext::new_rust(
14353        lsp::ServerCapabilities {
14354            completion_provider: Some(lsp::CompletionOptions {
14355                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14356                ..lsp::CompletionOptions::default()
14357            }),
14358            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14359            ..lsp::ServerCapabilities::default()
14360        },
14361        cx,
14362    )
14363    .await;
14364
14365    let _completion_requests_handler =
14366        cx.lsp
14367            .server
14368            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14369                Ok(Some(lsp::CompletionResponse::Array(vec![
14370                    lsp::CompletionItem {
14371                        label: "first".into(),
14372                        ..lsp::CompletionItem::default()
14373                    },
14374                    lsp::CompletionItem {
14375                        label: "last".into(),
14376                        ..lsp::CompletionItem::default()
14377                    },
14378                ])))
14379            });
14380
14381    cx.set_state(indoc! {"ˇ
14382        first
14383        last
14384        second
14385    "});
14386    cx.simulate_keystroke(".");
14387    cx.executor().run_until_parked();
14388    cx.condition(|editor, _| editor.context_menu_visible())
14389        .await;
14390    cx.update_editor(|editor, _, _| {
14391        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14392        {
14393            assert_eq!(
14394                completion_menu_entries(menu),
14395                &["first", "last", "second"],
14396                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
14397            );
14398        } else {
14399            panic!("expected completion menu to be open");
14400        }
14401    });
14402}
14403
14404#[gpui::test]
14405async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
14406    init_test(cx, |language_settings| {
14407        language_settings.defaults.completions = Some(CompletionSettings {
14408            words: WordsCompletionMode::Disabled,
14409            words_min_length: 0,
14410            lsp: true,
14411            lsp_fetch_timeout_ms: 0,
14412            lsp_insert_mode: LspInsertMode::Insert,
14413        });
14414    });
14415
14416    let mut cx = EditorLspTestContext::new_rust(
14417        lsp::ServerCapabilities {
14418            completion_provider: Some(lsp::CompletionOptions {
14419                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14420                ..lsp::CompletionOptions::default()
14421            }),
14422            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14423            ..lsp::ServerCapabilities::default()
14424        },
14425        cx,
14426    )
14427    .await;
14428
14429    let _completion_requests_handler =
14430        cx.lsp
14431            .server
14432            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14433                panic!("LSP completions should not be queried when dealing with word completions")
14434            });
14435
14436    cx.set_state(indoc! {"ˇ
14437        first
14438        last
14439        second
14440    "});
14441    cx.update_editor(|editor, window, cx| {
14442        editor.show_word_completions(&ShowWordCompletions, window, cx);
14443    });
14444    cx.executor().run_until_parked();
14445    cx.condition(|editor, _| editor.context_menu_visible())
14446        .await;
14447    cx.update_editor(|editor, _, _| {
14448        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14449        {
14450            assert_eq!(
14451                completion_menu_entries(menu),
14452                &["first", "last", "second"],
14453                "`ShowWordCompletions` action should show word completions"
14454            );
14455        } else {
14456            panic!("expected completion menu to be open");
14457        }
14458    });
14459
14460    cx.simulate_keystroke("l");
14461    cx.executor().run_until_parked();
14462    cx.condition(|editor, _| editor.context_menu_visible())
14463        .await;
14464    cx.update_editor(|editor, _, _| {
14465        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14466        {
14467            assert_eq!(
14468                completion_menu_entries(menu),
14469                &["last"],
14470                "After showing word completions, further editing should filter them and not query the LSP"
14471            );
14472        } else {
14473            panic!("expected completion menu to be open");
14474        }
14475    });
14476}
14477
14478#[gpui::test]
14479async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
14480    init_test(cx, |language_settings| {
14481        language_settings.defaults.completions = Some(CompletionSettings {
14482            words: WordsCompletionMode::Fallback,
14483            words_min_length: 0,
14484            lsp: false,
14485            lsp_fetch_timeout_ms: 0,
14486            lsp_insert_mode: LspInsertMode::Insert,
14487        });
14488    });
14489
14490    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14491
14492    cx.set_state(indoc! {"ˇ
14493        0_usize
14494        let
14495        33
14496        4.5f32
14497    "});
14498    cx.update_editor(|editor, window, cx| {
14499        editor.show_completions(&ShowCompletions::default(), window, cx);
14500    });
14501    cx.executor().run_until_parked();
14502    cx.condition(|editor, _| editor.context_menu_visible())
14503        .await;
14504    cx.update_editor(|editor, window, cx| {
14505        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14506        {
14507            assert_eq!(
14508                completion_menu_entries(menu),
14509                &["let"],
14510                "With no digits in the completion query, no digits should be in the word completions"
14511            );
14512        } else {
14513            panic!("expected completion menu to be open");
14514        }
14515        editor.cancel(&Cancel, window, cx);
14516    });
14517
14518    cx.set_state(indoc! {"14519        0_usize
14520        let
14521        3
14522        33.35f32
14523    "});
14524    cx.update_editor(|editor, window, cx| {
14525        editor.show_completions(&ShowCompletions::default(), window, cx);
14526    });
14527    cx.executor().run_until_parked();
14528    cx.condition(|editor, _| editor.context_menu_visible())
14529        .await;
14530    cx.update_editor(|editor, _, _| {
14531        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14532        {
14533            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
14534                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
14535        } else {
14536            panic!("expected completion menu to be open");
14537        }
14538    });
14539}
14540
14541#[gpui::test]
14542async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
14543    init_test(cx, |language_settings| {
14544        language_settings.defaults.completions = Some(CompletionSettings {
14545            words: WordsCompletionMode::Enabled,
14546            words_min_length: 3,
14547            lsp: true,
14548            lsp_fetch_timeout_ms: 0,
14549            lsp_insert_mode: LspInsertMode::Insert,
14550        });
14551    });
14552
14553    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14554    cx.set_state(indoc! {"ˇ
14555        wow
14556        wowen
14557        wowser
14558    "});
14559    cx.simulate_keystroke("w");
14560    cx.executor().run_until_parked();
14561    cx.update_editor(|editor, _, _| {
14562        if editor.context_menu.borrow_mut().is_some() {
14563            panic!(
14564                "expected completion menu to be hidden, as words completion threshold is not met"
14565            );
14566        }
14567    });
14568
14569    cx.update_editor(|editor, window, cx| {
14570        editor.show_word_completions(&ShowWordCompletions, window, cx);
14571    });
14572    cx.executor().run_until_parked();
14573    cx.update_editor(|editor, window, cx| {
14574        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14575        {
14576            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");
14577        } else {
14578            panic!("expected completion menu to be open after the word completions are called with an action");
14579        }
14580
14581        editor.cancel(&Cancel, window, cx);
14582    });
14583    cx.update_editor(|editor, _, _| {
14584        if editor.context_menu.borrow_mut().is_some() {
14585            panic!("expected completion menu to be hidden after canceling");
14586        }
14587    });
14588
14589    cx.simulate_keystroke("o");
14590    cx.executor().run_until_parked();
14591    cx.update_editor(|editor, _, _| {
14592        if editor.context_menu.borrow_mut().is_some() {
14593            panic!(
14594                "expected completion menu to be hidden, as words completion threshold is not met still"
14595            );
14596        }
14597    });
14598
14599    cx.simulate_keystroke("w");
14600    cx.executor().run_until_parked();
14601    cx.update_editor(|editor, _, _| {
14602        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14603        {
14604            assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
14605        } else {
14606            panic!("expected completion menu to be open after the word completions threshold is met");
14607        }
14608    });
14609}
14610
14611#[gpui::test]
14612async fn test_word_completions_disabled(cx: &mut TestAppContext) {
14613    init_test(cx, |language_settings| {
14614        language_settings.defaults.completions = Some(CompletionSettings {
14615            words: WordsCompletionMode::Enabled,
14616            words_min_length: 0,
14617            lsp: true,
14618            lsp_fetch_timeout_ms: 0,
14619            lsp_insert_mode: LspInsertMode::Insert,
14620        });
14621    });
14622
14623    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14624    cx.update_editor(|editor, _, _| {
14625        editor.disable_word_completions();
14626    });
14627    cx.set_state(indoc! {"ˇ
14628        wow
14629        wowen
14630        wowser
14631    "});
14632    cx.simulate_keystroke("w");
14633    cx.executor().run_until_parked();
14634    cx.update_editor(|editor, _, _| {
14635        if editor.context_menu.borrow_mut().is_some() {
14636            panic!(
14637                "expected completion menu to be hidden, as words completion are disabled for this editor"
14638            );
14639        }
14640    });
14641
14642    cx.update_editor(|editor, window, cx| {
14643        editor.show_word_completions(&ShowWordCompletions, window, cx);
14644    });
14645    cx.executor().run_until_parked();
14646    cx.update_editor(|editor, _, _| {
14647        if editor.context_menu.borrow_mut().is_some() {
14648            panic!(
14649                "expected completion menu to be hidden even if called for explicitly, as words completion are disabled for this editor"
14650            );
14651        }
14652    });
14653}
14654
14655fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
14656    let position = || lsp::Position {
14657        line: params.text_document_position.position.line,
14658        character: params.text_document_position.position.character,
14659    };
14660    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14661        range: lsp::Range {
14662            start: position(),
14663            end: position(),
14664        },
14665        new_text: text.to_string(),
14666    }))
14667}
14668
14669#[gpui::test]
14670async fn test_multiline_completion(cx: &mut TestAppContext) {
14671    init_test(cx, |_| {});
14672
14673    let fs = FakeFs::new(cx.executor());
14674    fs.insert_tree(
14675        path!("/a"),
14676        json!({
14677            "main.ts": "a",
14678        }),
14679    )
14680    .await;
14681
14682    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14683    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14684    let typescript_language = Arc::new(Language::new(
14685        LanguageConfig {
14686            name: "TypeScript".into(),
14687            matcher: LanguageMatcher {
14688                path_suffixes: vec!["ts".to_string()],
14689                ..LanguageMatcher::default()
14690            },
14691            line_comments: vec!["// ".into()],
14692            ..LanguageConfig::default()
14693        },
14694        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
14695    ));
14696    language_registry.add(typescript_language.clone());
14697    let mut fake_servers = language_registry.register_fake_lsp(
14698        "TypeScript",
14699        FakeLspAdapter {
14700            capabilities: lsp::ServerCapabilities {
14701                completion_provider: Some(lsp::CompletionOptions {
14702                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14703                    ..lsp::CompletionOptions::default()
14704                }),
14705                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14706                ..lsp::ServerCapabilities::default()
14707            },
14708            // Emulate vtsls label generation
14709            label_for_completion: Some(Box::new(|item, _| {
14710                let text = if let Some(description) = item
14711                    .label_details
14712                    .as_ref()
14713                    .and_then(|label_details| label_details.description.as_ref())
14714                {
14715                    format!("{} {}", item.label, description)
14716                } else if let Some(detail) = &item.detail {
14717                    format!("{} {}", item.label, detail)
14718                } else {
14719                    item.label.clone()
14720                };
14721                let len = text.len();
14722                Some(language::CodeLabel {
14723                    text,
14724                    runs: Vec::new(),
14725                    filter_range: 0..len,
14726                })
14727            })),
14728            ..FakeLspAdapter::default()
14729        },
14730    );
14731    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14732    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14733    let worktree_id = workspace
14734        .update(cx, |workspace, _window, cx| {
14735            workspace.project().update(cx, |project, cx| {
14736                project.worktrees(cx).next().unwrap().read(cx).id()
14737            })
14738        })
14739        .unwrap();
14740    let _buffer = project
14741        .update(cx, |project, cx| {
14742            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
14743        })
14744        .await
14745        .unwrap();
14746    let editor = workspace
14747        .update(cx, |workspace, window, cx| {
14748            workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
14749        })
14750        .unwrap()
14751        .await
14752        .unwrap()
14753        .downcast::<Editor>()
14754        .unwrap();
14755    let fake_server = fake_servers.next().await.unwrap();
14756
14757    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
14758    let multiline_label_2 = "a\nb\nc\n";
14759    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
14760    let multiline_description = "d\ne\nf\n";
14761    let multiline_detail_2 = "g\nh\ni\n";
14762
14763    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
14764        move |params, _| async move {
14765            Ok(Some(lsp::CompletionResponse::Array(vec![
14766                lsp::CompletionItem {
14767                    label: multiline_label.to_string(),
14768                    text_edit: gen_text_edit(&params, "new_text_1"),
14769                    ..lsp::CompletionItem::default()
14770                },
14771                lsp::CompletionItem {
14772                    label: "single line label 1".to_string(),
14773                    detail: Some(multiline_detail.to_string()),
14774                    text_edit: gen_text_edit(&params, "new_text_2"),
14775                    ..lsp::CompletionItem::default()
14776                },
14777                lsp::CompletionItem {
14778                    label: "single line label 2".to_string(),
14779                    label_details: Some(lsp::CompletionItemLabelDetails {
14780                        description: Some(multiline_description.to_string()),
14781                        detail: None,
14782                    }),
14783                    text_edit: gen_text_edit(&params, "new_text_2"),
14784                    ..lsp::CompletionItem::default()
14785                },
14786                lsp::CompletionItem {
14787                    label: multiline_label_2.to_string(),
14788                    detail: Some(multiline_detail_2.to_string()),
14789                    text_edit: gen_text_edit(&params, "new_text_3"),
14790                    ..lsp::CompletionItem::default()
14791                },
14792                lsp::CompletionItem {
14793                    label: "Label with many     spaces and \t but without newlines".to_string(),
14794                    detail: Some(
14795                        "Details with many     spaces and \t but without newlines".to_string(),
14796                    ),
14797                    text_edit: gen_text_edit(&params, "new_text_4"),
14798                    ..lsp::CompletionItem::default()
14799                },
14800            ])))
14801        },
14802    );
14803
14804    editor.update_in(cx, |editor, window, cx| {
14805        cx.focus_self(window);
14806        editor.move_to_end(&MoveToEnd, window, cx);
14807        editor.handle_input(".", window, cx);
14808    });
14809    cx.run_until_parked();
14810    completion_handle.next().await.unwrap();
14811
14812    editor.update(cx, |editor, _| {
14813        assert!(editor.context_menu_visible());
14814        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14815        {
14816            let completion_labels = menu
14817                .completions
14818                .borrow()
14819                .iter()
14820                .map(|c| c.label.text.clone())
14821                .collect::<Vec<_>>();
14822            assert_eq!(
14823                completion_labels,
14824                &[
14825                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
14826                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
14827                    "single line label 2 d e f ",
14828                    "a b c g h i ",
14829                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
14830                ],
14831                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
14832            );
14833
14834            for completion in menu
14835                .completions
14836                .borrow()
14837                .iter() {
14838                    assert_eq!(
14839                        completion.label.filter_range,
14840                        0..completion.label.text.len(),
14841                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
14842                    );
14843                }
14844        } else {
14845            panic!("expected completion menu to be open");
14846        }
14847    });
14848}
14849
14850#[gpui::test]
14851async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
14852    init_test(cx, |_| {});
14853    let mut cx = EditorLspTestContext::new_rust(
14854        lsp::ServerCapabilities {
14855            completion_provider: Some(lsp::CompletionOptions {
14856                trigger_characters: Some(vec![".".to_string()]),
14857                ..Default::default()
14858            }),
14859            ..Default::default()
14860        },
14861        cx,
14862    )
14863    .await;
14864    cx.lsp
14865        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14866            Ok(Some(lsp::CompletionResponse::Array(vec![
14867                lsp::CompletionItem {
14868                    label: "first".into(),
14869                    ..Default::default()
14870                },
14871                lsp::CompletionItem {
14872                    label: "last".into(),
14873                    ..Default::default()
14874                },
14875            ])))
14876        });
14877    cx.set_state("variableˇ");
14878    cx.simulate_keystroke(".");
14879    cx.executor().run_until_parked();
14880
14881    cx.update_editor(|editor, _, _| {
14882        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14883        {
14884            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
14885        } else {
14886            panic!("expected completion menu to be open");
14887        }
14888    });
14889
14890    cx.update_editor(|editor, window, cx| {
14891        editor.move_page_down(&MovePageDown::default(), window, cx);
14892        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14893        {
14894            assert!(
14895                menu.selected_item == 1,
14896                "expected PageDown to select the last item from the context menu"
14897            );
14898        } else {
14899            panic!("expected completion menu to stay open after PageDown");
14900        }
14901    });
14902
14903    cx.update_editor(|editor, window, cx| {
14904        editor.move_page_up(&MovePageUp::default(), window, cx);
14905        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14906        {
14907            assert!(
14908                menu.selected_item == 0,
14909                "expected PageUp to select the first item from the context menu"
14910            );
14911        } else {
14912            panic!("expected completion menu to stay open after PageUp");
14913        }
14914    });
14915}
14916
14917#[gpui::test]
14918async fn test_as_is_completions(cx: &mut TestAppContext) {
14919    init_test(cx, |_| {});
14920    let mut cx = EditorLspTestContext::new_rust(
14921        lsp::ServerCapabilities {
14922            completion_provider: Some(lsp::CompletionOptions {
14923                ..Default::default()
14924            }),
14925            ..Default::default()
14926        },
14927        cx,
14928    )
14929    .await;
14930    cx.lsp
14931        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14932            Ok(Some(lsp::CompletionResponse::Array(vec![
14933                lsp::CompletionItem {
14934                    label: "unsafe".into(),
14935                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14936                        range: lsp::Range {
14937                            start: lsp::Position {
14938                                line: 1,
14939                                character: 2,
14940                            },
14941                            end: lsp::Position {
14942                                line: 1,
14943                                character: 3,
14944                            },
14945                        },
14946                        new_text: "unsafe".to_string(),
14947                    })),
14948                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
14949                    ..Default::default()
14950                },
14951            ])))
14952        });
14953    cx.set_state("fn a() {}\n");
14954    cx.executor().run_until_parked();
14955    cx.update_editor(|editor, window, cx| {
14956        editor.show_completions(
14957            &ShowCompletions {
14958                trigger: Some("\n".into()),
14959            },
14960            window,
14961            cx,
14962        );
14963    });
14964    cx.executor().run_until_parked();
14965
14966    cx.update_editor(|editor, window, cx| {
14967        editor.confirm_completion(&Default::default(), window, cx)
14968    });
14969    cx.executor().run_until_parked();
14970    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
14971}
14972
14973#[gpui::test]
14974async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
14975    init_test(cx, |_| {});
14976    let language =
14977        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
14978    let mut cx = EditorLspTestContext::new(
14979        language,
14980        lsp::ServerCapabilities {
14981            completion_provider: Some(lsp::CompletionOptions {
14982                ..lsp::CompletionOptions::default()
14983            }),
14984            ..lsp::ServerCapabilities::default()
14985        },
14986        cx,
14987    )
14988    .await;
14989
14990    cx.set_state(
14991        "#ifndef BAR_H
14992#define BAR_H
14993
14994#include <stdbool.h>
14995
14996int fn_branch(bool do_branch1, bool do_branch2);
14997
14998#endif // BAR_H
14999ˇ",
15000    );
15001    cx.executor().run_until_parked();
15002    cx.update_editor(|editor, window, cx| {
15003        editor.handle_input("#", window, cx);
15004    });
15005    cx.executor().run_until_parked();
15006    cx.update_editor(|editor, window, cx| {
15007        editor.handle_input("i", window, cx);
15008    });
15009    cx.executor().run_until_parked();
15010    cx.update_editor(|editor, window, cx| {
15011        editor.handle_input("n", window, cx);
15012    });
15013    cx.executor().run_until_parked();
15014    cx.assert_editor_state(
15015        "#ifndef BAR_H
15016#define BAR_H
15017
15018#include <stdbool.h>
15019
15020int fn_branch(bool do_branch1, bool do_branch2);
15021
15022#endif // BAR_H
15023#inˇ",
15024    );
15025
15026    cx.lsp
15027        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15028            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15029                is_incomplete: false,
15030                item_defaults: None,
15031                items: vec![lsp::CompletionItem {
15032                    kind: Some(lsp::CompletionItemKind::SNIPPET),
15033                    label_details: Some(lsp::CompletionItemLabelDetails {
15034                        detail: Some("header".to_string()),
15035                        description: None,
15036                    }),
15037                    label: " include".to_string(),
15038                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15039                        range: lsp::Range {
15040                            start: lsp::Position {
15041                                line: 8,
15042                                character: 1,
15043                            },
15044                            end: lsp::Position {
15045                                line: 8,
15046                                character: 1,
15047                            },
15048                        },
15049                        new_text: "include \"$0\"".to_string(),
15050                    })),
15051                    sort_text: Some("40b67681include".to_string()),
15052                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15053                    filter_text: Some("include".to_string()),
15054                    insert_text: Some("include \"$0\"".to_string()),
15055                    ..lsp::CompletionItem::default()
15056                }],
15057            })))
15058        });
15059    cx.update_editor(|editor, window, cx| {
15060        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15061    });
15062    cx.executor().run_until_parked();
15063    cx.update_editor(|editor, window, cx| {
15064        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15065    });
15066    cx.executor().run_until_parked();
15067    cx.assert_editor_state(
15068        "#ifndef BAR_H
15069#define BAR_H
15070
15071#include <stdbool.h>
15072
15073int fn_branch(bool do_branch1, bool do_branch2);
15074
15075#endif // BAR_H
15076#include \"ˇ\"",
15077    );
15078
15079    cx.lsp
15080        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15081            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15082                is_incomplete: true,
15083                item_defaults: None,
15084                items: vec![lsp::CompletionItem {
15085                    kind: Some(lsp::CompletionItemKind::FILE),
15086                    label: "AGL/".to_string(),
15087                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15088                        range: lsp::Range {
15089                            start: lsp::Position {
15090                                line: 8,
15091                                character: 10,
15092                            },
15093                            end: lsp::Position {
15094                                line: 8,
15095                                character: 11,
15096                            },
15097                        },
15098                        new_text: "AGL/".to_string(),
15099                    })),
15100                    sort_text: Some("40b67681AGL/".to_string()),
15101                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
15102                    filter_text: Some("AGL/".to_string()),
15103                    insert_text: Some("AGL/".to_string()),
15104                    ..lsp::CompletionItem::default()
15105                }],
15106            })))
15107        });
15108    cx.update_editor(|editor, window, cx| {
15109        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15110    });
15111    cx.executor().run_until_parked();
15112    cx.update_editor(|editor, window, cx| {
15113        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15114    });
15115    cx.executor().run_until_parked();
15116    cx.assert_editor_state(
15117        r##"#ifndef BAR_H
15118#define BAR_H
15119
15120#include <stdbool.h>
15121
15122int fn_branch(bool do_branch1, bool do_branch2);
15123
15124#endif // BAR_H
15125#include "AGL/ˇ"##,
15126    );
15127
15128    cx.update_editor(|editor, window, cx| {
15129        editor.handle_input("\"", window, cx);
15130    });
15131    cx.executor().run_until_parked();
15132    cx.assert_editor_state(
15133        r##"#ifndef BAR_H
15134#define BAR_H
15135
15136#include <stdbool.h>
15137
15138int fn_branch(bool do_branch1, bool do_branch2);
15139
15140#endif // BAR_H
15141#include "AGL/"ˇ"##,
15142    );
15143}
15144
15145#[gpui::test]
15146async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
15147    init_test(cx, |_| {});
15148
15149    let mut cx = EditorLspTestContext::new_rust(
15150        lsp::ServerCapabilities {
15151            completion_provider: Some(lsp::CompletionOptions {
15152                trigger_characters: Some(vec![".".to_string()]),
15153                resolve_provider: Some(true),
15154                ..Default::default()
15155            }),
15156            ..Default::default()
15157        },
15158        cx,
15159    )
15160    .await;
15161
15162    cx.set_state("fn main() { let a = 2ˇ; }");
15163    cx.simulate_keystroke(".");
15164    let completion_item = lsp::CompletionItem {
15165        label: "Some".into(),
15166        kind: Some(lsp::CompletionItemKind::SNIPPET),
15167        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15168        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15169            kind: lsp::MarkupKind::Markdown,
15170            value: "```rust\nSome(2)\n```".to_string(),
15171        })),
15172        deprecated: Some(false),
15173        sort_text: Some("Some".to_string()),
15174        filter_text: Some("Some".to_string()),
15175        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15176        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15177            range: lsp::Range {
15178                start: lsp::Position {
15179                    line: 0,
15180                    character: 22,
15181                },
15182                end: lsp::Position {
15183                    line: 0,
15184                    character: 22,
15185                },
15186            },
15187            new_text: "Some(2)".to_string(),
15188        })),
15189        additional_text_edits: Some(vec![lsp::TextEdit {
15190            range: lsp::Range {
15191                start: lsp::Position {
15192                    line: 0,
15193                    character: 20,
15194                },
15195                end: lsp::Position {
15196                    line: 0,
15197                    character: 22,
15198                },
15199            },
15200            new_text: "".to_string(),
15201        }]),
15202        ..Default::default()
15203    };
15204
15205    let closure_completion_item = completion_item.clone();
15206    let counter = Arc::new(AtomicUsize::new(0));
15207    let counter_clone = counter.clone();
15208    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15209        let task_completion_item = closure_completion_item.clone();
15210        counter_clone.fetch_add(1, atomic::Ordering::Release);
15211        async move {
15212            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15213                is_incomplete: true,
15214                item_defaults: None,
15215                items: vec![task_completion_item],
15216            })))
15217        }
15218    });
15219
15220    cx.condition(|editor, _| editor.context_menu_visible())
15221        .await;
15222    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
15223    assert!(request.next().await.is_some());
15224    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15225
15226    cx.simulate_keystrokes("S o m");
15227    cx.condition(|editor, _| editor.context_menu_visible())
15228        .await;
15229    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
15230    assert!(request.next().await.is_some());
15231    assert!(request.next().await.is_some());
15232    assert!(request.next().await.is_some());
15233    request.close();
15234    assert!(request.next().await.is_none());
15235    assert_eq!(
15236        counter.load(atomic::Ordering::Acquire),
15237        4,
15238        "With the completions menu open, only one LSP request should happen per input"
15239    );
15240}
15241
15242#[gpui::test]
15243async fn test_toggle_comment(cx: &mut TestAppContext) {
15244    init_test(cx, |_| {});
15245    let mut cx = EditorTestContext::new(cx).await;
15246    let language = Arc::new(Language::new(
15247        LanguageConfig {
15248            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15249            ..Default::default()
15250        },
15251        Some(tree_sitter_rust::LANGUAGE.into()),
15252    ));
15253    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15254
15255    // If multiple selections intersect a line, the line is only toggled once.
15256    cx.set_state(indoc! {"
15257        fn a() {
15258            «//b();
15259            ˇ»// «c();
15260            //ˇ»  d();
15261        }
15262    "});
15263
15264    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15265
15266    cx.assert_editor_state(indoc! {"
15267        fn a() {
15268            «b();
15269            c();
15270            ˇ» d();
15271        }
15272    "});
15273
15274    // The comment prefix is inserted at the same column for every line in a
15275    // selection.
15276    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15277
15278    cx.assert_editor_state(indoc! {"
15279        fn a() {
15280            // «b();
15281            // c();
15282            ˇ»//  d();
15283        }
15284    "});
15285
15286    // If a selection ends at the beginning of a line, that line is not toggled.
15287    cx.set_selections_state(indoc! {"
15288        fn a() {
15289            // b();
15290            «// c();
15291        ˇ»    //  d();
15292        }
15293    "});
15294
15295    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15296
15297    cx.assert_editor_state(indoc! {"
15298        fn a() {
15299            // b();
15300            «c();
15301        ˇ»    //  d();
15302        }
15303    "});
15304
15305    // If a selection span a single line and is empty, the line is toggled.
15306    cx.set_state(indoc! {"
15307        fn a() {
15308            a();
15309            b();
15310        ˇ
15311        }
15312    "});
15313
15314    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15315
15316    cx.assert_editor_state(indoc! {"
15317        fn a() {
15318            a();
15319            b();
15320        //•ˇ
15321        }
15322    "});
15323
15324    // If a selection span multiple lines, empty lines are not toggled.
15325    cx.set_state(indoc! {"
15326        fn a() {
15327            «a();
15328
15329            c();ˇ»
15330        }
15331    "});
15332
15333    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15334
15335    cx.assert_editor_state(indoc! {"
15336        fn a() {
15337            // «a();
15338
15339            // c();ˇ»
15340        }
15341    "});
15342
15343    // If a selection includes multiple comment prefixes, all lines are uncommented.
15344    cx.set_state(indoc! {"
15345        fn a() {
15346            «// a();
15347            /// b();
15348            //! c();ˇ»
15349        }
15350    "});
15351
15352    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15353
15354    cx.assert_editor_state(indoc! {"
15355        fn a() {
15356            «a();
15357            b();
15358            c();ˇ»
15359        }
15360    "});
15361}
15362
15363#[gpui::test]
15364async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
15365    init_test(cx, |_| {});
15366    let mut cx = EditorTestContext::new(cx).await;
15367    let language = Arc::new(Language::new(
15368        LanguageConfig {
15369            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15370            ..Default::default()
15371        },
15372        Some(tree_sitter_rust::LANGUAGE.into()),
15373    ));
15374    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15375
15376    let toggle_comments = &ToggleComments {
15377        advance_downwards: false,
15378        ignore_indent: true,
15379    };
15380
15381    // If multiple selections intersect a line, the line is only toggled once.
15382    cx.set_state(indoc! {"
15383        fn a() {
15384        //    «b();
15385        //    c();
15386        //    ˇ» d();
15387        }
15388    "});
15389
15390    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15391
15392    cx.assert_editor_state(indoc! {"
15393        fn a() {
15394            «b();
15395            c();
15396            ˇ» d();
15397        }
15398    "});
15399
15400    // The comment prefix is inserted at the beginning of each line
15401    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15402
15403    cx.assert_editor_state(indoc! {"
15404        fn a() {
15405        //    «b();
15406        //    c();
15407        //    ˇ» d();
15408        }
15409    "});
15410
15411    // If a selection ends at the beginning of a line, that line is not toggled.
15412    cx.set_selections_state(indoc! {"
15413        fn a() {
15414        //    b();
15415        //    «c();
15416        ˇ»//     d();
15417        }
15418    "});
15419
15420    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15421
15422    cx.assert_editor_state(indoc! {"
15423        fn a() {
15424        //    b();
15425            «c();
15426        ˇ»//     d();
15427        }
15428    "});
15429
15430    // If a selection span a single line and is empty, the line is toggled.
15431    cx.set_state(indoc! {"
15432        fn a() {
15433            a();
15434            b();
15435        ˇ
15436        }
15437    "});
15438
15439    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15440
15441    cx.assert_editor_state(indoc! {"
15442        fn a() {
15443            a();
15444            b();
15445        //ˇ
15446        }
15447    "});
15448
15449    // If a selection span multiple lines, empty lines are not toggled.
15450    cx.set_state(indoc! {"
15451        fn a() {
15452            «a();
15453
15454            c();ˇ»
15455        }
15456    "});
15457
15458    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15459
15460    cx.assert_editor_state(indoc! {"
15461        fn a() {
15462        //    «a();
15463
15464        //    c();ˇ»
15465        }
15466    "});
15467
15468    // If a selection includes multiple comment prefixes, all lines are uncommented.
15469    cx.set_state(indoc! {"
15470        fn a() {
15471        //    «a();
15472        ///    b();
15473        //!    c();ˇ»
15474        }
15475    "});
15476
15477    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15478
15479    cx.assert_editor_state(indoc! {"
15480        fn a() {
15481            «a();
15482            b();
15483            c();ˇ»
15484        }
15485    "});
15486}
15487
15488#[gpui::test]
15489async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
15490    init_test(cx, |_| {});
15491
15492    let language = Arc::new(Language::new(
15493        LanguageConfig {
15494            line_comments: vec!["// ".into()],
15495            ..Default::default()
15496        },
15497        Some(tree_sitter_rust::LANGUAGE.into()),
15498    ));
15499
15500    let mut cx = EditorTestContext::new(cx).await;
15501
15502    cx.language_registry().add(language.clone());
15503    cx.update_buffer(|buffer, cx| {
15504        buffer.set_language(Some(language), cx);
15505    });
15506
15507    let toggle_comments = &ToggleComments {
15508        advance_downwards: true,
15509        ignore_indent: false,
15510    };
15511
15512    // Single cursor on one line -> advance
15513    // Cursor moves horizontally 3 characters as well on non-blank line
15514    cx.set_state(indoc!(
15515        "fn a() {
15516             ˇdog();
15517             cat();
15518        }"
15519    ));
15520    cx.update_editor(|editor, window, cx| {
15521        editor.toggle_comments(toggle_comments, window, cx);
15522    });
15523    cx.assert_editor_state(indoc!(
15524        "fn a() {
15525             // dog();
15526             catˇ();
15527        }"
15528    ));
15529
15530    // Single selection on one line -> don't advance
15531    cx.set_state(indoc!(
15532        "fn a() {
15533             «dog()ˇ»;
15534             cat();
15535        }"
15536    ));
15537    cx.update_editor(|editor, window, cx| {
15538        editor.toggle_comments(toggle_comments, window, cx);
15539    });
15540    cx.assert_editor_state(indoc!(
15541        "fn a() {
15542             // «dog()ˇ»;
15543             cat();
15544        }"
15545    ));
15546
15547    // Multiple cursors on one line -> advance
15548    cx.set_state(indoc!(
15549        "fn a() {
15550             ˇdˇog();
15551             cat();
15552        }"
15553    ));
15554    cx.update_editor(|editor, window, cx| {
15555        editor.toggle_comments(toggle_comments, window, cx);
15556    });
15557    cx.assert_editor_state(indoc!(
15558        "fn a() {
15559             // dog();
15560             catˇ(ˇ);
15561        }"
15562    ));
15563
15564    // Multiple cursors on one line, with selection -> don't advance
15565    cx.set_state(indoc!(
15566        "fn a() {
15567             ˇdˇog«()ˇ»;
15568             cat();
15569        }"
15570    ));
15571    cx.update_editor(|editor, window, cx| {
15572        editor.toggle_comments(toggle_comments, window, cx);
15573    });
15574    cx.assert_editor_state(indoc!(
15575        "fn a() {
15576             // ˇdˇog«()ˇ»;
15577             cat();
15578        }"
15579    ));
15580
15581    // Single cursor on one line -> advance
15582    // Cursor moves to column 0 on blank line
15583    cx.set_state(indoc!(
15584        "fn a() {
15585             ˇdog();
15586
15587             cat();
15588        }"
15589    ));
15590    cx.update_editor(|editor, window, cx| {
15591        editor.toggle_comments(toggle_comments, window, cx);
15592    });
15593    cx.assert_editor_state(indoc!(
15594        "fn a() {
15595             // dog();
15596        ˇ
15597             cat();
15598        }"
15599    ));
15600
15601    // Single cursor on one line -> advance
15602    // Cursor starts and ends at column 0
15603    cx.set_state(indoc!(
15604        "fn a() {
15605         ˇ    dog();
15606             cat();
15607        }"
15608    ));
15609    cx.update_editor(|editor, window, cx| {
15610        editor.toggle_comments(toggle_comments, window, cx);
15611    });
15612    cx.assert_editor_state(indoc!(
15613        "fn a() {
15614             // dog();
15615         ˇ    cat();
15616        }"
15617    ));
15618}
15619
15620#[gpui::test]
15621async fn test_toggle_block_comment(cx: &mut TestAppContext) {
15622    init_test(cx, |_| {});
15623
15624    let mut cx = EditorTestContext::new(cx).await;
15625
15626    let html_language = Arc::new(
15627        Language::new(
15628            LanguageConfig {
15629                name: "HTML".into(),
15630                block_comment: Some(BlockCommentConfig {
15631                    start: "<!-- ".into(),
15632                    prefix: "".into(),
15633                    end: " -->".into(),
15634                    tab_size: 0,
15635                }),
15636                ..Default::default()
15637            },
15638            Some(tree_sitter_html::LANGUAGE.into()),
15639        )
15640        .with_injection_query(
15641            r#"
15642            (script_element
15643                (raw_text) @injection.content
15644                (#set! injection.language "javascript"))
15645            "#,
15646        )
15647        .unwrap(),
15648    );
15649
15650    let javascript_language = Arc::new(Language::new(
15651        LanguageConfig {
15652            name: "JavaScript".into(),
15653            line_comments: vec!["// ".into()],
15654            ..Default::default()
15655        },
15656        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
15657    ));
15658
15659    cx.language_registry().add(html_language.clone());
15660    cx.language_registry().add(javascript_language);
15661    cx.update_buffer(|buffer, cx| {
15662        buffer.set_language(Some(html_language), cx);
15663    });
15664
15665    // Toggle comments for empty selections
15666    cx.set_state(
15667        &r#"
15668            <p>A</p>ˇ
15669            <p>B</p>ˇ
15670            <p>C</p>ˇ
15671        "#
15672        .unindent(),
15673    );
15674    cx.update_editor(|editor, window, cx| {
15675        editor.toggle_comments(&ToggleComments::default(), window, cx)
15676    });
15677    cx.assert_editor_state(
15678        &r#"
15679            <!-- <p>A</p>ˇ -->
15680            <!-- <p>B</p>ˇ -->
15681            <!-- <p>C</p>ˇ -->
15682        "#
15683        .unindent(),
15684    );
15685    cx.update_editor(|editor, window, cx| {
15686        editor.toggle_comments(&ToggleComments::default(), window, cx)
15687    });
15688    cx.assert_editor_state(
15689        &r#"
15690            <p>A</p>ˇ
15691            <p>B</p>ˇ
15692            <p>C</p>ˇ
15693        "#
15694        .unindent(),
15695    );
15696
15697    // Toggle comments for mixture of empty and non-empty selections, where
15698    // multiple selections occupy a given line.
15699    cx.set_state(
15700        &r#"
15701            <p>A«</p>
15702            <p>ˇ»B</p>ˇ
15703            <p>C«</p>
15704            <p>ˇ»D</p>ˇ
15705        "#
15706        .unindent(),
15707    );
15708
15709    cx.update_editor(|editor, window, cx| {
15710        editor.toggle_comments(&ToggleComments::default(), window, cx)
15711    });
15712    cx.assert_editor_state(
15713        &r#"
15714            <!-- <p>A«</p>
15715            <p>ˇ»B</p>ˇ -->
15716            <!-- <p>C«</p>
15717            <p>ˇ»D</p>ˇ -->
15718        "#
15719        .unindent(),
15720    );
15721    cx.update_editor(|editor, window, cx| {
15722        editor.toggle_comments(&ToggleComments::default(), window, cx)
15723    });
15724    cx.assert_editor_state(
15725        &r#"
15726            <p>A«</p>
15727            <p>ˇ»B</p>ˇ
15728            <p>C«</p>
15729            <p>ˇ»D</p>ˇ
15730        "#
15731        .unindent(),
15732    );
15733
15734    // Toggle comments when different languages are active for different
15735    // selections.
15736    cx.set_state(
15737        &r#"
15738            ˇ<script>
15739                ˇvar x = new Y();
15740            ˇ</script>
15741        "#
15742        .unindent(),
15743    );
15744    cx.executor().run_until_parked();
15745    cx.update_editor(|editor, window, cx| {
15746        editor.toggle_comments(&ToggleComments::default(), window, cx)
15747    });
15748    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
15749    // Uncommenting and commenting from this position brings in even more wrong artifacts.
15750    cx.assert_editor_state(
15751        &r#"
15752            <!-- ˇ<script> -->
15753                // ˇvar x = new Y();
15754            <!-- ˇ</script> -->
15755        "#
15756        .unindent(),
15757    );
15758}
15759
15760#[gpui::test]
15761fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
15762    init_test(cx, |_| {});
15763
15764    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15765    let multibuffer = cx.new(|cx| {
15766        let mut multibuffer = MultiBuffer::new(ReadWrite);
15767        multibuffer.push_excerpts(
15768            buffer.clone(),
15769            [
15770                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
15771                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
15772            ],
15773            cx,
15774        );
15775        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
15776        multibuffer
15777    });
15778
15779    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
15780    editor.update_in(cx, |editor, window, cx| {
15781        assert_eq!(editor.text(cx), "aaaa\nbbbb");
15782        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15783            s.select_ranges([
15784                Point::new(0, 0)..Point::new(0, 0),
15785                Point::new(1, 0)..Point::new(1, 0),
15786            ])
15787        });
15788
15789        editor.handle_input("X", window, cx);
15790        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
15791        assert_eq!(
15792            editor.selections.ranges(cx),
15793            [
15794                Point::new(0, 1)..Point::new(0, 1),
15795                Point::new(1, 1)..Point::new(1, 1),
15796            ]
15797        );
15798
15799        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
15800        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15801            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
15802        });
15803        editor.backspace(&Default::default(), window, cx);
15804        assert_eq!(editor.text(cx), "Xa\nbbb");
15805        assert_eq!(
15806            editor.selections.ranges(cx),
15807            [Point::new(1, 0)..Point::new(1, 0)]
15808        );
15809
15810        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15811            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
15812        });
15813        editor.backspace(&Default::default(), window, cx);
15814        assert_eq!(editor.text(cx), "X\nbb");
15815        assert_eq!(
15816            editor.selections.ranges(cx),
15817            [Point::new(0, 1)..Point::new(0, 1)]
15818        );
15819    });
15820}
15821
15822#[gpui::test]
15823fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
15824    init_test(cx, |_| {});
15825
15826    let markers = vec![('[', ']').into(), ('(', ')').into()];
15827    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
15828        indoc! {"
15829            [aaaa
15830            (bbbb]
15831            cccc)",
15832        },
15833        markers.clone(),
15834    );
15835    let excerpt_ranges = markers.into_iter().map(|marker| {
15836        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
15837        ExcerptRange::new(context)
15838    });
15839    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
15840    let multibuffer = cx.new(|cx| {
15841        let mut multibuffer = MultiBuffer::new(ReadWrite);
15842        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
15843        multibuffer
15844    });
15845
15846    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
15847    editor.update_in(cx, |editor, window, cx| {
15848        let (expected_text, selection_ranges) = marked_text_ranges(
15849            indoc! {"
15850                aaaa
15851                bˇbbb
15852                bˇbbˇb
15853                cccc"
15854            },
15855            true,
15856        );
15857        assert_eq!(editor.text(cx), expected_text);
15858        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15859            s.select_ranges(selection_ranges)
15860        });
15861
15862        editor.handle_input("X", window, cx);
15863
15864        let (expected_text, expected_selections) = marked_text_ranges(
15865            indoc! {"
15866                aaaa
15867                bXˇbbXb
15868                bXˇbbXˇb
15869                cccc"
15870            },
15871            false,
15872        );
15873        assert_eq!(editor.text(cx), expected_text);
15874        assert_eq!(editor.selections.ranges(cx), expected_selections);
15875
15876        editor.newline(&Newline, window, cx);
15877        let (expected_text, expected_selections) = marked_text_ranges(
15878            indoc! {"
15879                aaaa
15880                bX
15881                ˇbbX
15882                b
15883                bX
15884                ˇbbX
15885                ˇb
15886                cccc"
15887            },
15888            false,
15889        );
15890        assert_eq!(editor.text(cx), expected_text);
15891        assert_eq!(editor.selections.ranges(cx), expected_selections);
15892    });
15893}
15894
15895#[gpui::test]
15896fn test_refresh_selections(cx: &mut TestAppContext) {
15897    init_test(cx, |_| {});
15898
15899    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15900    let mut excerpt1_id = None;
15901    let multibuffer = cx.new(|cx| {
15902        let mut multibuffer = MultiBuffer::new(ReadWrite);
15903        excerpt1_id = multibuffer
15904            .push_excerpts(
15905                buffer.clone(),
15906                [
15907                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
15908                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
15909                ],
15910                cx,
15911            )
15912            .into_iter()
15913            .next();
15914        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
15915        multibuffer
15916    });
15917
15918    let editor = cx.add_window(|window, cx| {
15919        let mut editor = build_editor(multibuffer.clone(), window, cx);
15920        let snapshot = editor.snapshot(window, cx);
15921        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15922            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
15923        });
15924        editor.begin_selection(
15925            Point::new(2, 1).to_display_point(&snapshot),
15926            true,
15927            1,
15928            window,
15929            cx,
15930        );
15931        assert_eq!(
15932            editor.selections.ranges(cx),
15933            [
15934                Point::new(1, 3)..Point::new(1, 3),
15935                Point::new(2, 1)..Point::new(2, 1),
15936            ]
15937        );
15938        editor
15939    });
15940
15941    // Refreshing selections is a no-op when excerpts haven't changed.
15942    _ = editor.update(cx, |editor, window, cx| {
15943        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
15944        assert_eq!(
15945            editor.selections.ranges(cx),
15946            [
15947                Point::new(1, 3)..Point::new(1, 3),
15948                Point::new(2, 1)..Point::new(2, 1),
15949            ]
15950        );
15951    });
15952
15953    multibuffer.update(cx, |multibuffer, cx| {
15954        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
15955    });
15956    _ = editor.update(cx, |editor, window, cx| {
15957        // Removing an excerpt causes the first selection to become degenerate.
15958        assert_eq!(
15959            editor.selections.ranges(cx),
15960            [
15961                Point::new(0, 0)..Point::new(0, 0),
15962                Point::new(0, 1)..Point::new(0, 1)
15963            ]
15964        );
15965
15966        // Refreshing selections will relocate the first selection to the original buffer
15967        // location.
15968        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
15969        assert_eq!(
15970            editor.selections.ranges(cx),
15971            [
15972                Point::new(0, 1)..Point::new(0, 1),
15973                Point::new(0, 3)..Point::new(0, 3)
15974            ]
15975        );
15976        assert!(editor.selections.pending_anchor().is_some());
15977    });
15978}
15979
15980#[gpui::test]
15981fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
15982    init_test(cx, |_| {});
15983
15984    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15985    let mut excerpt1_id = None;
15986    let multibuffer = cx.new(|cx| {
15987        let mut multibuffer = MultiBuffer::new(ReadWrite);
15988        excerpt1_id = multibuffer
15989            .push_excerpts(
15990                buffer.clone(),
15991                [
15992                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
15993                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
15994                ],
15995                cx,
15996            )
15997            .into_iter()
15998            .next();
15999        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
16000        multibuffer
16001    });
16002
16003    let editor = cx.add_window(|window, cx| {
16004        let mut editor = build_editor(multibuffer.clone(), window, cx);
16005        let snapshot = editor.snapshot(window, cx);
16006        editor.begin_selection(
16007            Point::new(1, 3).to_display_point(&snapshot),
16008            false,
16009            1,
16010            window,
16011            cx,
16012        );
16013        assert_eq!(
16014            editor.selections.ranges(cx),
16015            [Point::new(1, 3)..Point::new(1, 3)]
16016        );
16017        editor
16018    });
16019
16020    multibuffer.update(cx, |multibuffer, cx| {
16021        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
16022    });
16023    _ = editor.update(cx, |editor, window, cx| {
16024        assert_eq!(
16025            editor.selections.ranges(cx),
16026            [Point::new(0, 0)..Point::new(0, 0)]
16027        );
16028
16029        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
16030        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16031        assert_eq!(
16032            editor.selections.ranges(cx),
16033            [Point::new(0, 3)..Point::new(0, 3)]
16034        );
16035        assert!(editor.selections.pending_anchor().is_some());
16036    });
16037}
16038
16039#[gpui::test]
16040async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
16041    init_test(cx, |_| {});
16042
16043    let language = Arc::new(
16044        Language::new(
16045            LanguageConfig {
16046                brackets: BracketPairConfig {
16047                    pairs: vec![
16048                        BracketPair {
16049                            start: "{".to_string(),
16050                            end: "}".to_string(),
16051                            close: true,
16052                            surround: true,
16053                            newline: true,
16054                        },
16055                        BracketPair {
16056                            start: "/* ".to_string(),
16057                            end: " */".to_string(),
16058                            close: true,
16059                            surround: true,
16060                            newline: true,
16061                        },
16062                    ],
16063                    ..Default::default()
16064                },
16065                ..Default::default()
16066            },
16067            Some(tree_sitter_rust::LANGUAGE.into()),
16068        )
16069        .with_indents_query("")
16070        .unwrap(),
16071    );
16072
16073    let text = concat!(
16074        "{   }\n",     //
16075        "  x\n",       //
16076        "  /*   */\n", //
16077        "x\n",         //
16078        "{{} }\n",     //
16079    );
16080
16081    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
16082    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
16083    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
16084    editor
16085        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
16086        .await;
16087
16088    editor.update_in(cx, |editor, window, cx| {
16089        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16090            s.select_display_ranges([
16091                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
16092                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
16093                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
16094            ])
16095        });
16096        editor.newline(&Newline, window, cx);
16097
16098        assert_eq!(
16099            editor.buffer().read(cx).read(cx).text(),
16100            concat!(
16101                "{ \n",    // Suppress rustfmt
16102                "\n",      //
16103                "}\n",     //
16104                "  x\n",   //
16105                "  /* \n", //
16106                "  \n",    //
16107                "  */\n",  //
16108                "x\n",     //
16109                "{{} \n",  //
16110                "}\n",     //
16111            )
16112        );
16113    });
16114}
16115
16116#[gpui::test]
16117fn test_highlighted_ranges(cx: &mut TestAppContext) {
16118    init_test(cx, |_| {});
16119
16120    let editor = cx.add_window(|window, cx| {
16121        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
16122        build_editor(buffer, window, cx)
16123    });
16124
16125    _ = editor.update(cx, |editor, window, cx| {
16126        struct Type1;
16127        struct Type2;
16128
16129        let buffer = editor.buffer.read(cx).snapshot(cx);
16130
16131        let anchor_range =
16132            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
16133
16134        editor.highlight_background::<Type1>(
16135            &[
16136                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
16137                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
16138                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
16139                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
16140            ],
16141            |_| Hsla::red(),
16142            cx,
16143        );
16144        editor.highlight_background::<Type2>(
16145            &[
16146                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
16147                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
16148                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
16149                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
16150            ],
16151            |_| Hsla::green(),
16152            cx,
16153        );
16154
16155        let snapshot = editor.snapshot(window, cx);
16156        let highlighted_ranges = editor.sorted_background_highlights_in_range(
16157            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
16158            &snapshot,
16159            cx.theme(),
16160        );
16161        assert_eq!(
16162            highlighted_ranges,
16163            &[
16164                (
16165                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
16166                    Hsla::green(),
16167                ),
16168                (
16169                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
16170                    Hsla::red(),
16171                ),
16172                (
16173                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
16174                    Hsla::green(),
16175                ),
16176                (
16177                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16178                    Hsla::red(),
16179                ),
16180            ]
16181        );
16182        assert_eq!(
16183            editor.sorted_background_highlights_in_range(
16184                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
16185                &snapshot,
16186                cx.theme(),
16187            ),
16188            &[(
16189                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16190                Hsla::red(),
16191            )]
16192        );
16193    });
16194}
16195
16196#[gpui::test]
16197async fn test_following(cx: &mut TestAppContext) {
16198    init_test(cx, |_| {});
16199
16200    let fs = FakeFs::new(cx.executor());
16201    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16202
16203    let buffer = project.update(cx, |project, cx| {
16204        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, false, cx);
16205        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
16206    });
16207    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
16208    let follower = cx.update(|cx| {
16209        cx.open_window(
16210            WindowOptions {
16211                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
16212                    gpui::Point::new(px(0.), px(0.)),
16213                    gpui::Point::new(px(10.), px(80.)),
16214                ))),
16215                ..Default::default()
16216            },
16217            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
16218        )
16219        .unwrap()
16220    });
16221
16222    let is_still_following = Rc::new(RefCell::new(true));
16223    let follower_edit_event_count = Rc::new(RefCell::new(0));
16224    let pending_update = Rc::new(RefCell::new(None));
16225    let leader_entity = leader.root(cx).unwrap();
16226    let follower_entity = follower.root(cx).unwrap();
16227    _ = follower.update(cx, {
16228        let update = pending_update.clone();
16229        let is_still_following = is_still_following.clone();
16230        let follower_edit_event_count = follower_edit_event_count.clone();
16231        |_, window, cx| {
16232            cx.subscribe_in(
16233                &leader_entity,
16234                window,
16235                move |_, leader, event, window, cx| {
16236                    leader.read(cx).add_event_to_update_proto(
16237                        event,
16238                        &mut update.borrow_mut(),
16239                        window,
16240                        cx,
16241                    );
16242                },
16243            )
16244            .detach();
16245
16246            cx.subscribe_in(
16247                &follower_entity,
16248                window,
16249                move |_, _, event: &EditorEvent, _window, _cx| {
16250                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
16251                        *is_still_following.borrow_mut() = false;
16252                    }
16253
16254                    if let EditorEvent::BufferEdited = event {
16255                        *follower_edit_event_count.borrow_mut() += 1;
16256                    }
16257                },
16258            )
16259            .detach();
16260        }
16261    });
16262
16263    // Update the selections only
16264    _ = leader.update(cx, |leader, window, cx| {
16265        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16266            s.select_ranges([1..1])
16267        });
16268    });
16269    follower
16270        .update(cx, |follower, window, cx| {
16271            follower.apply_update_proto(
16272                &project,
16273                pending_update.borrow_mut().take().unwrap(),
16274                window,
16275                cx,
16276            )
16277        })
16278        .unwrap()
16279        .await
16280        .unwrap();
16281    _ = follower.update(cx, |follower, _, cx| {
16282        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
16283    });
16284    assert!(*is_still_following.borrow());
16285    assert_eq!(*follower_edit_event_count.borrow(), 0);
16286
16287    // Update the scroll position only
16288    _ = leader.update(cx, |leader, window, cx| {
16289        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16290    });
16291    follower
16292        .update(cx, |follower, window, cx| {
16293            follower.apply_update_proto(
16294                &project,
16295                pending_update.borrow_mut().take().unwrap(),
16296                window,
16297                cx,
16298            )
16299        })
16300        .unwrap()
16301        .await
16302        .unwrap();
16303    assert_eq!(
16304        follower
16305            .update(cx, |follower, _, cx| follower.scroll_position(cx))
16306            .unwrap(),
16307        gpui::Point::new(1.5, 3.5)
16308    );
16309    assert!(*is_still_following.borrow());
16310    assert_eq!(*follower_edit_event_count.borrow(), 0);
16311
16312    // Update the selections and scroll position. The follower's scroll position is updated
16313    // via autoscroll, not via the leader's exact scroll position.
16314    _ = leader.update(cx, |leader, window, cx| {
16315        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16316            s.select_ranges([0..0])
16317        });
16318        leader.request_autoscroll(Autoscroll::newest(), cx);
16319        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16320    });
16321    follower
16322        .update(cx, |follower, window, cx| {
16323            follower.apply_update_proto(
16324                &project,
16325                pending_update.borrow_mut().take().unwrap(),
16326                window,
16327                cx,
16328            )
16329        })
16330        .unwrap()
16331        .await
16332        .unwrap();
16333    _ = follower.update(cx, |follower, _, cx| {
16334        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
16335        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
16336    });
16337    assert!(*is_still_following.borrow());
16338
16339    // Creating a pending selection that precedes another selection
16340    _ = leader.update(cx, |leader, window, cx| {
16341        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16342            s.select_ranges([1..1])
16343        });
16344        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
16345    });
16346    follower
16347        .update(cx, |follower, window, cx| {
16348            follower.apply_update_proto(
16349                &project,
16350                pending_update.borrow_mut().take().unwrap(),
16351                window,
16352                cx,
16353            )
16354        })
16355        .unwrap()
16356        .await
16357        .unwrap();
16358    _ = follower.update(cx, |follower, _, cx| {
16359        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
16360    });
16361    assert!(*is_still_following.borrow());
16362
16363    // Extend the pending selection so that it surrounds another selection
16364    _ = leader.update(cx, |leader, window, cx| {
16365        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
16366    });
16367    follower
16368        .update(cx, |follower, window, cx| {
16369            follower.apply_update_proto(
16370                &project,
16371                pending_update.borrow_mut().take().unwrap(),
16372                window,
16373                cx,
16374            )
16375        })
16376        .unwrap()
16377        .await
16378        .unwrap();
16379    _ = follower.update(cx, |follower, _, cx| {
16380        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
16381    });
16382
16383    // Scrolling locally breaks the follow
16384    _ = follower.update(cx, |follower, window, cx| {
16385        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
16386        follower.set_scroll_anchor(
16387            ScrollAnchor {
16388                anchor: top_anchor,
16389                offset: gpui::Point::new(0.0, 0.5),
16390            },
16391            window,
16392            cx,
16393        );
16394    });
16395    assert!(!(*is_still_following.borrow()));
16396}
16397
16398#[gpui::test]
16399async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
16400    init_test(cx, |_| {});
16401
16402    let fs = FakeFs::new(cx.executor());
16403    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16404    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16405    let pane = workspace
16406        .update(cx, |workspace, _, _| workspace.active_pane().clone())
16407        .unwrap();
16408
16409    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16410
16411    let leader = pane.update_in(cx, |_, window, cx| {
16412        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
16413        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
16414    });
16415
16416    // Start following the editor when it has no excerpts.
16417    let mut state_message =
16418        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16419    let workspace_entity = workspace.root(cx).unwrap();
16420    let follower_1 = cx
16421        .update_window(*workspace.deref(), |_, window, cx| {
16422            Editor::from_state_proto(
16423                workspace_entity,
16424                ViewId {
16425                    creator: CollaboratorId::PeerId(PeerId::default()),
16426                    id: 0,
16427                },
16428                &mut state_message,
16429                window,
16430                cx,
16431            )
16432        })
16433        .unwrap()
16434        .unwrap()
16435        .await
16436        .unwrap();
16437
16438    let update_message = Rc::new(RefCell::new(None));
16439    follower_1.update_in(cx, {
16440        let update = update_message.clone();
16441        |_, window, cx| {
16442            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
16443                leader.read(cx).add_event_to_update_proto(
16444                    event,
16445                    &mut update.borrow_mut(),
16446                    window,
16447                    cx,
16448                );
16449            })
16450            .detach();
16451        }
16452    });
16453
16454    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
16455        (
16456            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, false, cx),
16457            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, false, cx),
16458        )
16459    });
16460
16461    // Insert some excerpts.
16462    leader.update(cx, |leader, cx| {
16463        leader.buffer.update(cx, |multibuffer, cx| {
16464            multibuffer.set_excerpts_for_path(
16465                PathKey::namespaced(1, Arc::from(Path::new("b.txt"))),
16466                buffer_1.clone(),
16467                vec![
16468                    Point::row_range(0..3),
16469                    Point::row_range(1..6),
16470                    Point::row_range(12..15),
16471                ],
16472                0,
16473                cx,
16474            );
16475            multibuffer.set_excerpts_for_path(
16476                PathKey::namespaced(1, Arc::from(Path::new("a.txt"))),
16477                buffer_2.clone(),
16478                vec![Point::row_range(0..6), Point::row_range(8..12)],
16479                0,
16480                cx,
16481            );
16482        });
16483    });
16484
16485    // Apply the update of adding the excerpts.
16486    follower_1
16487        .update_in(cx, |follower, window, cx| {
16488            follower.apply_update_proto(
16489                &project,
16490                update_message.borrow().clone().unwrap(),
16491                window,
16492                cx,
16493            )
16494        })
16495        .await
16496        .unwrap();
16497    assert_eq!(
16498        follower_1.update(cx, |editor, cx| editor.text(cx)),
16499        leader.update(cx, |editor, cx| editor.text(cx))
16500    );
16501    update_message.borrow_mut().take();
16502
16503    // Start following separately after it already has excerpts.
16504    let mut state_message =
16505        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16506    let workspace_entity = workspace.root(cx).unwrap();
16507    let follower_2 = cx
16508        .update_window(*workspace.deref(), |_, window, cx| {
16509            Editor::from_state_proto(
16510                workspace_entity,
16511                ViewId {
16512                    creator: CollaboratorId::PeerId(PeerId::default()),
16513                    id: 0,
16514                },
16515                &mut state_message,
16516                window,
16517                cx,
16518            )
16519        })
16520        .unwrap()
16521        .unwrap()
16522        .await
16523        .unwrap();
16524    assert_eq!(
16525        follower_2.update(cx, |editor, cx| editor.text(cx)),
16526        leader.update(cx, |editor, cx| editor.text(cx))
16527    );
16528
16529    // Remove some excerpts.
16530    leader.update(cx, |leader, cx| {
16531        leader.buffer.update(cx, |multibuffer, cx| {
16532            let excerpt_ids = multibuffer.excerpt_ids();
16533            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
16534            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
16535        });
16536    });
16537
16538    // Apply the update of removing the excerpts.
16539    follower_1
16540        .update_in(cx, |follower, window, cx| {
16541            follower.apply_update_proto(
16542                &project,
16543                update_message.borrow().clone().unwrap(),
16544                window,
16545                cx,
16546            )
16547        })
16548        .await
16549        .unwrap();
16550    follower_2
16551        .update_in(cx, |follower, window, cx| {
16552            follower.apply_update_proto(
16553                &project,
16554                update_message.borrow().clone().unwrap(),
16555                window,
16556                cx,
16557            )
16558        })
16559        .await
16560        .unwrap();
16561    update_message.borrow_mut().take();
16562    assert_eq!(
16563        follower_1.update(cx, |editor, cx| editor.text(cx)),
16564        leader.update(cx, |editor, cx| editor.text(cx))
16565    );
16566}
16567
16568#[gpui::test]
16569async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16570    init_test(cx, |_| {});
16571
16572    let mut cx = EditorTestContext::new(cx).await;
16573    let lsp_store =
16574        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
16575
16576    cx.set_state(indoc! {"
16577        ˇfn func(abc def: i32) -> u32 {
16578        }
16579    "});
16580
16581    cx.update(|_, cx| {
16582        lsp_store.update(cx, |lsp_store, cx| {
16583            lsp_store
16584                .update_diagnostics(
16585                    LanguageServerId(0),
16586                    lsp::PublishDiagnosticsParams {
16587                        uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
16588                        version: None,
16589                        diagnostics: vec![
16590                            lsp::Diagnostic {
16591                                range: lsp::Range::new(
16592                                    lsp::Position::new(0, 11),
16593                                    lsp::Position::new(0, 12),
16594                                ),
16595                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16596                                ..Default::default()
16597                            },
16598                            lsp::Diagnostic {
16599                                range: lsp::Range::new(
16600                                    lsp::Position::new(0, 12),
16601                                    lsp::Position::new(0, 15),
16602                                ),
16603                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16604                                ..Default::default()
16605                            },
16606                            lsp::Diagnostic {
16607                                range: lsp::Range::new(
16608                                    lsp::Position::new(0, 25),
16609                                    lsp::Position::new(0, 28),
16610                                ),
16611                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16612                                ..Default::default()
16613                            },
16614                        ],
16615                    },
16616                    None,
16617                    DiagnosticSourceKind::Pushed,
16618                    &[],
16619                    cx,
16620                )
16621                .unwrap()
16622        });
16623    });
16624
16625    executor.run_until_parked();
16626
16627    cx.update_editor(|editor, window, cx| {
16628        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16629    });
16630
16631    cx.assert_editor_state(indoc! {"
16632        fn func(abc def: i32) -> ˇu32 {
16633        }
16634    "});
16635
16636    cx.update_editor(|editor, window, cx| {
16637        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16638    });
16639
16640    cx.assert_editor_state(indoc! {"
16641        fn func(abc ˇdef: i32) -> u32 {
16642        }
16643    "});
16644
16645    cx.update_editor(|editor, window, cx| {
16646        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16647    });
16648
16649    cx.assert_editor_state(indoc! {"
16650        fn func(abcˇ def: i32) -> u32 {
16651        }
16652    "});
16653
16654    cx.update_editor(|editor, window, cx| {
16655        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16656    });
16657
16658    cx.assert_editor_state(indoc! {"
16659        fn func(abc def: i32) -> ˇu32 {
16660        }
16661    "});
16662}
16663
16664#[gpui::test]
16665async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16666    init_test(cx, |_| {});
16667
16668    let mut cx = EditorTestContext::new(cx).await;
16669
16670    let diff_base = r#"
16671        use some::mod;
16672
16673        const A: u32 = 42;
16674
16675        fn main() {
16676            println!("hello");
16677
16678            println!("world");
16679        }
16680        "#
16681    .unindent();
16682
16683    // Edits are modified, removed, modified, added
16684    cx.set_state(
16685        &r#"
16686        use some::modified;
16687
16688        ˇ
16689        fn main() {
16690            println!("hello there");
16691
16692            println!("around the");
16693            println!("world");
16694        }
16695        "#
16696        .unindent(),
16697    );
16698
16699    cx.set_head_text(&diff_base);
16700    executor.run_until_parked();
16701
16702    cx.update_editor(|editor, window, cx| {
16703        //Wrap around the bottom of the buffer
16704        for _ in 0..3 {
16705            editor.go_to_next_hunk(&GoToHunk, window, cx);
16706        }
16707    });
16708
16709    cx.assert_editor_state(
16710        &r#"
16711        ˇuse some::modified;
16712
16713
16714        fn main() {
16715            println!("hello there");
16716
16717            println!("around the");
16718            println!("world");
16719        }
16720        "#
16721        .unindent(),
16722    );
16723
16724    cx.update_editor(|editor, window, cx| {
16725        //Wrap around the top of the buffer
16726        for _ in 0..2 {
16727            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16728        }
16729    });
16730
16731    cx.assert_editor_state(
16732        &r#"
16733        use some::modified;
16734
16735
16736        fn main() {
16737        ˇ    println!("hello there");
16738
16739            println!("around the");
16740            println!("world");
16741        }
16742        "#
16743        .unindent(),
16744    );
16745
16746    cx.update_editor(|editor, window, cx| {
16747        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16748    });
16749
16750    cx.assert_editor_state(
16751        &r#"
16752        use some::modified;
16753
16754        ˇ
16755        fn main() {
16756            println!("hello there");
16757
16758            println!("around the");
16759            println!("world");
16760        }
16761        "#
16762        .unindent(),
16763    );
16764
16765    cx.update_editor(|editor, window, cx| {
16766        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16767    });
16768
16769    cx.assert_editor_state(
16770        &r#"
16771        ˇuse some::modified;
16772
16773
16774        fn main() {
16775            println!("hello there");
16776
16777            println!("around the");
16778            println!("world");
16779        }
16780        "#
16781        .unindent(),
16782    );
16783
16784    cx.update_editor(|editor, window, cx| {
16785        for _ in 0..2 {
16786            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16787        }
16788    });
16789
16790    cx.assert_editor_state(
16791        &r#"
16792        use some::modified;
16793
16794
16795        fn main() {
16796        ˇ    println!("hello there");
16797
16798            println!("around the");
16799            println!("world");
16800        }
16801        "#
16802        .unindent(),
16803    );
16804
16805    cx.update_editor(|editor, window, cx| {
16806        editor.fold(&Fold, window, cx);
16807    });
16808
16809    cx.update_editor(|editor, window, cx| {
16810        editor.go_to_next_hunk(&GoToHunk, window, cx);
16811    });
16812
16813    cx.assert_editor_state(
16814        &r#"
16815        ˇuse some::modified;
16816
16817
16818        fn main() {
16819            println!("hello there");
16820
16821            println!("around the");
16822            println!("world");
16823        }
16824        "#
16825        .unindent(),
16826    );
16827}
16828
16829#[test]
16830fn test_split_words() {
16831    fn split(text: &str) -> Vec<&str> {
16832        split_words(text).collect()
16833    }
16834
16835    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
16836    assert_eq!(split("hello_world"), &["hello_", "world"]);
16837    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
16838    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
16839    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
16840    assert_eq!(split("helloworld"), &["helloworld"]);
16841
16842    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
16843}
16844
16845#[gpui::test]
16846async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
16847    init_test(cx, |_| {});
16848
16849    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
16850    let mut assert = |before, after| {
16851        let _state_context = cx.set_state(before);
16852        cx.run_until_parked();
16853        cx.update_editor(|editor, window, cx| {
16854            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
16855        });
16856        cx.run_until_parked();
16857        cx.assert_editor_state(after);
16858    };
16859
16860    // Outside bracket jumps to outside of matching bracket
16861    assert("console.logˇ(var);", "console.log(var)ˇ;");
16862    assert("console.log(var)ˇ;", "console.logˇ(var);");
16863
16864    // Inside bracket jumps to inside of matching bracket
16865    assert("console.log(ˇvar);", "console.log(varˇ);");
16866    assert("console.log(varˇ);", "console.log(ˇvar);");
16867
16868    // When outside a bracket and inside, favor jumping to the inside bracket
16869    assert(
16870        "console.log('foo', [1, 2, 3]ˇ);",
16871        "console.log(ˇ'foo', [1, 2, 3]);",
16872    );
16873    assert(
16874        "console.log(ˇ'foo', [1, 2, 3]);",
16875        "console.log('foo', [1, 2, 3]ˇ);",
16876    );
16877
16878    // Bias forward if two options are equally likely
16879    assert(
16880        "let result = curried_fun()ˇ();",
16881        "let result = curried_fun()()ˇ;",
16882    );
16883
16884    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
16885    assert(
16886        indoc! {"
16887            function test() {
16888                console.log('test')ˇ
16889            }"},
16890        indoc! {"
16891            function test() {
16892                console.logˇ('test')
16893            }"},
16894    );
16895}
16896
16897#[gpui::test]
16898async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
16899    init_test(cx, |_| {});
16900
16901    let fs = FakeFs::new(cx.executor());
16902    fs.insert_tree(
16903        path!("/a"),
16904        json!({
16905            "main.rs": "fn main() { let a = 5; }",
16906            "other.rs": "// Test file",
16907        }),
16908    )
16909    .await;
16910    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16911
16912    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
16913    language_registry.add(Arc::new(Language::new(
16914        LanguageConfig {
16915            name: "Rust".into(),
16916            matcher: LanguageMatcher {
16917                path_suffixes: vec!["rs".to_string()],
16918                ..Default::default()
16919            },
16920            brackets: BracketPairConfig {
16921                pairs: vec![BracketPair {
16922                    start: "{".to_string(),
16923                    end: "}".to_string(),
16924                    close: true,
16925                    surround: true,
16926                    newline: true,
16927                }],
16928                disabled_scopes_by_bracket_ix: Vec::new(),
16929            },
16930            ..Default::default()
16931        },
16932        Some(tree_sitter_rust::LANGUAGE.into()),
16933    )));
16934    let mut fake_servers = language_registry.register_fake_lsp(
16935        "Rust",
16936        FakeLspAdapter {
16937            capabilities: lsp::ServerCapabilities {
16938                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
16939                    first_trigger_character: "{".to_string(),
16940                    more_trigger_character: None,
16941                }),
16942                ..Default::default()
16943            },
16944            ..Default::default()
16945        },
16946    );
16947
16948    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16949
16950    let cx = &mut VisualTestContext::from_window(*workspace, cx);
16951
16952    let worktree_id = workspace
16953        .update(cx, |workspace, _, cx| {
16954            workspace.project().update(cx, |project, cx| {
16955                project.worktrees(cx).next().unwrap().read(cx).id()
16956            })
16957        })
16958        .unwrap();
16959
16960    let buffer = project
16961        .update(cx, |project, cx| {
16962            project.open_local_buffer(path!("/a/main.rs"), cx)
16963        })
16964        .await
16965        .unwrap();
16966    let editor_handle = workspace
16967        .update(cx, |workspace, window, cx| {
16968            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
16969        })
16970        .unwrap()
16971        .await
16972        .unwrap()
16973        .downcast::<Editor>()
16974        .unwrap();
16975
16976    cx.executor().start_waiting();
16977    let fake_server = fake_servers.next().await.unwrap();
16978
16979    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
16980        |params, _| async move {
16981            assert_eq!(
16982                params.text_document_position.text_document.uri,
16983                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
16984            );
16985            assert_eq!(
16986                params.text_document_position.position,
16987                lsp::Position::new(0, 21),
16988            );
16989
16990            Ok(Some(vec![lsp::TextEdit {
16991                new_text: "]".to_string(),
16992                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
16993            }]))
16994        },
16995    );
16996
16997    editor_handle.update_in(cx, |editor, window, cx| {
16998        window.focus(&editor.focus_handle(cx));
16999        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17000            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
17001        });
17002        editor.handle_input("{", window, cx);
17003    });
17004
17005    cx.executor().run_until_parked();
17006
17007    buffer.update(cx, |buffer, _| {
17008        assert_eq!(
17009            buffer.text(),
17010            "fn main() { let a = {5}; }",
17011            "No extra braces from on type formatting should appear in the buffer"
17012        )
17013    });
17014}
17015
17016#[gpui::test(iterations = 20, seeds(31))]
17017async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
17018    init_test(cx, |_| {});
17019
17020    let mut cx = EditorLspTestContext::new_rust(
17021        lsp::ServerCapabilities {
17022            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
17023                first_trigger_character: ".".to_string(),
17024                more_trigger_character: None,
17025            }),
17026            ..Default::default()
17027        },
17028        cx,
17029    )
17030    .await;
17031
17032    cx.update_buffer(|buffer, _| {
17033        // This causes autoindent to be async.
17034        buffer.set_sync_parse_timeout(Duration::ZERO)
17035    });
17036
17037    cx.set_state("fn c() {\n    d()ˇ\n}\n");
17038    cx.simulate_keystroke("\n");
17039    cx.run_until_parked();
17040
17041    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
17042    let mut request =
17043        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
17044            let buffer_cloned = buffer_cloned.clone();
17045            async move {
17046                buffer_cloned.update(&mut cx, |buffer, _| {
17047                    assert_eq!(
17048                        buffer.text(),
17049                        "fn c() {\n    d()\n        .\n}\n",
17050                        "OnTypeFormatting should triggered after autoindent applied"
17051                    )
17052                })?;
17053
17054                Ok(Some(vec![]))
17055            }
17056        });
17057
17058    cx.simulate_keystroke(".");
17059    cx.run_until_parked();
17060
17061    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
17062    assert!(request.next().await.is_some());
17063    request.close();
17064    assert!(request.next().await.is_none());
17065}
17066
17067#[gpui::test]
17068async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
17069    init_test(cx, |_| {});
17070
17071    let fs = FakeFs::new(cx.executor());
17072    fs.insert_tree(
17073        path!("/a"),
17074        json!({
17075            "main.rs": "fn main() { let a = 5; }",
17076            "other.rs": "// Test file",
17077        }),
17078    )
17079    .await;
17080
17081    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17082
17083    let server_restarts = Arc::new(AtomicUsize::new(0));
17084    let closure_restarts = Arc::clone(&server_restarts);
17085    let language_server_name = "test language server";
17086    let language_name: LanguageName = "Rust".into();
17087
17088    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17089    language_registry.add(Arc::new(Language::new(
17090        LanguageConfig {
17091            name: language_name.clone(),
17092            matcher: LanguageMatcher {
17093                path_suffixes: vec!["rs".to_string()],
17094                ..Default::default()
17095            },
17096            ..Default::default()
17097        },
17098        Some(tree_sitter_rust::LANGUAGE.into()),
17099    )));
17100    let mut fake_servers = language_registry.register_fake_lsp(
17101        "Rust",
17102        FakeLspAdapter {
17103            name: language_server_name,
17104            initialization_options: Some(json!({
17105                "testOptionValue": true
17106            })),
17107            initializer: Some(Box::new(move |fake_server| {
17108                let task_restarts = Arc::clone(&closure_restarts);
17109                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
17110                    task_restarts.fetch_add(1, atomic::Ordering::Release);
17111                    futures::future::ready(Ok(()))
17112                });
17113            })),
17114            ..Default::default()
17115        },
17116    );
17117
17118    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17119    let _buffer = project
17120        .update(cx, |project, cx| {
17121            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
17122        })
17123        .await
17124        .unwrap();
17125    let _fake_server = fake_servers.next().await.unwrap();
17126    update_test_language_settings(cx, |language_settings| {
17127        language_settings.languages.0.insert(
17128            language_name.clone(),
17129            LanguageSettingsContent {
17130                tab_size: NonZeroU32::new(8),
17131                ..Default::default()
17132            },
17133        );
17134    });
17135    cx.executor().run_until_parked();
17136    assert_eq!(
17137        server_restarts.load(atomic::Ordering::Acquire),
17138        0,
17139        "Should not restart LSP server on an unrelated change"
17140    );
17141
17142    update_test_project_settings(cx, |project_settings| {
17143        project_settings.lsp.insert(
17144            "Some other server name".into(),
17145            LspSettings {
17146                binary: None,
17147                settings: None,
17148                initialization_options: Some(json!({
17149                    "some other init value": false
17150                })),
17151                enable_lsp_tasks: false,
17152                fetch: None,
17153            },
17154        );
17155    });
17156    cx.executor().run_until_parked();
17157    assert_eq!(
17158        server_restarts.load(atomic::Ordering::Acquire),
17159        0,
17160        "Should not restart LSP server on an unrelated LSP settings change"
17161    );
17162
17163    update_test_project_settings(cx, |project_settings| {
17164        project_settings.lsp.insert(
17165            language_server_name.into(),
17166            LspSettings {
17167                binary: None,
17168                settings: None,
17169                initialization_options: Some(json!({
17170                    "anotherInitValue": false
17171                })),
17172                enable_lsp_tasks: false,
17173                fetch: None,
17174            },
17175        );
17176    });
17177    cx.executor().run_until_parked();
17178    assert_eq!(
17179        server_restarts.load(atomic::Ordering::Acquire),
17180        1,
17181        "Should restart LSP server on a related LSP settings change"
17182    );
17183
17184    update_test_project_settings(cx, |project_settings| {
17185        project_settings.lsp.insert(
17186            language_server_name.into(),
17187            LspSettings {
17188                binary: None,
17189                settings: None,
17190                initialization_options: Some(json!({
17191                    "anotherInitValue": false
17192                })),
17193                enable_lsp_tasks: false,
17194                fetch: None,
17195            },
17196        );
17197    });
17198    cx.executor().run_until_parked();
17199    assert_eq!(
17200        server_restarts.load(atomic::Ordering::Acquire),
17201        1,
17202        "Should not restart LSP server on a related LSP settings change that is the same"
17203    );
17204
17205    update_test_project_settings(cx, |project_settings| {
17206        project_settings.lsp.insert(
17207            language_server_name.into(),
17208            LspSettings {
17209                binary: None,
17210                settings: None,
17211                initialization_options: None,
17212                enable_lsp_tasks: false,
17213                fetch: None,
17214            },
17215        );
17216    });
17217    cx.executor().run_until_parked();
17218    assert_eq!(
17219        server_restarts.load(atomic::Ordering::Acquire),
17220        2,
17221        "Should restart LSP server on another related LSP settings change"
17222    );
17223}
17224
17225#[gpui::test]
17226async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
17227    init_test(cx, |_| {});
17228
17229    let mut cx = EditorLspTestContext::new_rust(
17230        lsp::ServerCapabilities {
17231            completion_provider: Some(lsp::CompletionOptions {
17232                trigger_characters: Some(vec![".".to_string()]),
17233                resolve_provider: Some(true),
17234                ..Default::default()
17235            }),
17236            ..Default::default()
17237        },
17238        cx,
17239    )
17240    .await;
17241
17242    cx.set_state("fn main() { let a = 2ˇ; }");
17243    cx.simulate_keystroke(".");
17244    let completion_item = lsp::CompletionItem {
17245        label: "some".into(),
17246        kind: Some(lsp::CompletionItemKind::SNIPPET),
17247        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
17248        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
17249            kind: lsp::MarkupKind::Markdown,
17250            value: "```rust\nSome(2)\n```".to_string(),
17251        })),
17252        deprecated: Some(false),
17253        sort_text: Some("fffffff2".to_string()),
17254        filter_text: Some("some".to_string()),
17255        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
17256        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17257            range: lsp::Range {
17258                start: lsp::Position {
17259                    line: 0,
17260                    character: 22,
17261                },
17262                end: lsp::Position {
17263                    line: 0,
17264                    character: 22,
17265                },
17266            },
17267            new_text: "Some(2)".to_string(),
17268        })),
17269        additional_text_edits: Some(vec![lsp::TextEdit {
17270            range: lsp::Range {
17271                start: lsp::Position {
17272                    line: 0,
17273                    character: 20,
17274                },
17275                end: lsp::Position {
17276                    line: 0,
17277                    character: 22,
17278                },
17279            },
17280            new_text: "".to_string(),
17281        }]),
17282        ..Default::default()
17283    };
17284
17285    let closure_completion_item = completion_item.clone();
17286    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17287        let task_completion_item = closure_completion_item.clone();
17288        async move {
17289            Ok(Some(lsp::CompletionResponse::Array(vec![
17290                task_completion_item,
17291            ])))
17292        }
17293    });
17294
17295    request.next().await;
17296
17297    cx.condition(|editor, _| editor.context_menu_visible())
17298        .await;
17299    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
17300        editor
17301            .confirm_completion(&ConfirmCompletion::default(), window, cx)
17302            .unwrap()
17303    });
17304    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
17305
17306    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
17307        let task_completion_item = completion_item.clone();
17308        async move { Ok(task_completion_item) }
17309    })
17310    .next()
17311    .await
17312    .unwrap();
17313    apply_additional_edits.await.unwrap();
17314    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
17315}
17316
17317#[gpui::test]
17318async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
17319    init_test(cx, |_| {});
17320
17321    let mut cx = EditorLspTestContext::new_rust(
17322        lsp::ServerCapabilities {
17323            completion_provider: Some(lsp::CompletionOptions {
17324                trigger_characters: Some(vec![".".to_string()]),
17325                resolve_provider: Some(true),
17326                ..Default::default()
17327            }),
17328            ..Default::default()
17329        },
17330        cx,
17331    )
17332    .await;
17333
17334    cx.set_state("fn main() { let a = 2ˇ; }");
17335    cx.simulate_keystroke(".");
17336
17337    let item1 = lsp::CompletionItem {
17338        label: "method id()".to_string(),
17339        filter_text: Some("id".to_string()),
17340        detail: None,
17341        documentation: None,
17342        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17343            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17344            new_text: ".id".to_string(),
17345        })),
17346        ..lsp::CompletionItem::default()
17347    };
17348
17349    let item2 = lsp::CompletionItem {
17350        label: "other".to_string(),
17351        filter_text: Some("other".to_string()),
17352        detail: None,
17353        documentation: None,
17354        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17355            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17356            new_text: ".other".to_string(),
17357        })),
17358        ..lsp::CompletionItem::default()
17359    };
17360
17361    let item1 = item1.clone();
17362    cx.set_request_handler::<lsp::request::Completion, _, _>({
17363        let item1 = item1.clone();
17364        move |_, _, _| {
17365            let item1 = item1.clone();
17366            let item2 = item2.clone();
17367            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
17368        }
17369    })
17370    .next()
17371    .await;
17372
17373    cx.condition(|editor, _| editor.context_menu_visible())
17374        .await;
17375    cx.update_editor(|editor, _, _| {
17376        let context_menu = editor.context_menu.borrow_mut();
17377        let context_menu = context_menu
17378            .as_ref()
17379            .expect("Should have the context menu deployed");
17380        match context_menu {
17381            CodeContextMenu::Completions(completions_menu) => {
17382                let completions = completions_menu.completions.borrow_mut();
17383                assert_eq!(
17384                    completions
17385                        .iter()
17386                        .map(|completion| &completion.label.text)
17387                        .collect::<Vec<_>>(),
17388                    vec!["method id()", "other"]
17389                )
17390            }
17391            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17392        }
17393    });
17394
17395    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
17396        let item1 = item1.clone();
17397        move |_, item_to_resolve, _| {
17398            let item1 = item1.clone();
17399            async move {
17400                if item1 == item_to_resolve {
17401                    Ok(lsp::CompletionItem {
17402                        label: "method id()".to_string(),
17403                        filter_text: Some("id".to_string()),
17404                        detail: Some("Now resolved!".to_string()),
17405                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
17406                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17407                            range: lsp::Range::new(
17408                                lsp::Position::new(0, 22),
17409                                lsp::Position::new(0, 22),
17410                            ),
17411                            new_text: ".id".to_string(),
17412                        })),
17413                        ..lsp::CompletionItem::default()
17414                    })
17415                } else {
17416                    Ok(item_to_resolve)
17417                }
17418            }
17419        }
17420    })
17421    .next()
17422    .await
17423    .unwrap();
17424    cx.run_until_parked();
17425
17426    cx.update_editor(|editor, window, cx| {
17427        editor.context_menu_next(&Default::default(), window, cx);
17428    });
17429
17430    cx.update_editor(|editor, _, _| {
17431        let context_menu = editor.context_menu.borrow_mut();
17432        let context_menu = context_menu
17433            .as_ref()
17434            .expect("Should have the context menu deployed");
17435        match context_menu {
17436            CodeContextMenu::Completions(completions_menu) => {
17437                let completions = completions_menu.completions.borrow_mut();
17438                assert_eq!(
17439                    completions
17440                        .iter()
17441                        .map(|completion| &completion.label.text)
17442                        .collect::<Vec<_>>(),
17443                    vec!["method id() Now resolved!", "other"],
17444                    "Should update first completion label, but not second as the filter text did not match."
17445                );
17446            }
17447            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17448        }
17449    });
17450}
17451
17452#[gpui::test]
17453async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
17454    init_test(cx, |_| {});
17455    let mut cx = EditorLspTestContext::new_rust(
17456        lsp::ServerCapabilities {
17457            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
17458            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
17459            completion_provider: Some(lsp::CompletionOptions {
17460                resolve_provider: Some(true),
17461                ..Default::default()
17462            }),
17463            ..Default::default()
17464        },
17465        cx,
17466    )
17467    .await;
17468    cx.set_state(indoc! {"
17469        struct TestStruct {
17470            field: i32
17471        }
17472
17473        fn mainˇ() {
17474            let unused_var = 42;
17475            let test_struct = TestStruct { field: 42 };
17476        }
17477    "});
17478    let symbol_range = cx.lsp_range(indoc! {"
17479        struct TestStruct {
17480            field: i32
17481        }
17482
17483        «fn main»() {
17484            let unused_var = 42;
17485            let test_struct = TestStruct { field: 42 };
17486        }
17487    "});
17488    let mut hover_requests =
17489        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
17490            Ok(Some(lsp::Hover {
17491                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
17492                    kind: lsp::MarkupKind::Markdown,
17493                    value: "Function documentation".to_string(),
17494                }),
17495                range: Some(symbol_range),
17496            }))
17497        });
17498
17499    // Case 1: Test that code action menu hide hover popover
17500    cx.dispatch_action(Hover);
17501    hover_requests.next().await;
17502    cx.condition(|editor, _| editor.hover_state.visible()).await;
17503    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
17504        move |_, _, _| async move {
17505            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
17506                lsp::CodeAction {
17507                    title: "Remove unused variable".to_string(),
17508                    kind: Some(CodeActionKind::QUICKFIX),
17509                    edit: Some(lsp::WorkspaceEdit {
17510                        changes: Some(
17511                            [(
17512                                lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
17513                                vec![lsp::TextEdit {
17514                                    range: lsp::Range::new(
17515                                        lsp::Position::new(5, 4),
17516                                        lsp::Position::new(5, 27),
17517                                    ),
17518                                    new_text: "".to_string(),
17519                                }],
17520                            )]
17521                            .into_iter()
17522                            .collect(),
17523                        ),
17524                        ..Default::default()
17525                    }),
17526                    ..Default::default()
17527                },
17528            )]))
17529        },
17530    );
17531    cx.update_editor(|editor, window, cx| {
17532        editor.toggle_code_actions(
17533            &ToggleCodeActions {
17534                deployed_from: None,
17535                quick_launch: false,
17536            },
17537            window,
17538            cx,
17539        );
17540    });
17541    code_action_requests.next().await;
17542    cx.run_until_parked();
17543    cx.condition(|editor, _| editor.context_menu_visible())
17544        .await;
17545    cx.update_editor(|editor, _, _| {
17546        assert!(
17547            !editor.hover_state.visible(),
17548            "Hover popover should be hidden when code action menu is shown"
17549        );
17550        // Hide code actions
17551        editor.context_menu.take();
17552    });
17553
17554    // Case 2: Test that code completions hide hover popover
17555    cx.dispatch_action(Hover);
17556    hover_requests.next().await;
17557    cx.condition(|editor, _| editor.hover_state.visible()).await;
17558    let counter = Arc::new(AtomicUsize::new(0));
17559    let mut completion_requests =
17560        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17561            let counter = counter.clone();
17562            async move {
17563                counter.fetch_add(1, atomic::Ordering::Release);
17564                Ok(Some(lsp::CompletionResponse::Array(vec![
17565                    lsp::CompletionItem {
17566                        label: "main".into(),
17567                        kind: Some(lsp::CompletionItemKind::FUNCTION),
17568                        detail: Some("() -> ()".to_string()),
17569                        ..Default::default()
17570                    },
17571                    lsp::CompletionItem {
17572                        label: "TestStruct".into(),
17573                        kind: Some(lsp::CompletionItemKind::STRUCT),
17574                        detail: Some("struct TestStruct".to_string()),
17575                        ..Default::default()
17576                    },
17577                ])))
17578            }
17579        });
17580    cx.update_editor(|editor, window, cx| {
17581        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
17582    });
17583    completion_requests.next().await;
17584    cx.condition(|editor, _| editor.context_menu_visible())
17585        .await;
17586    cx.update_editor(|editor, _, _| {
17587        assert!(
17588            !editor.hover_state.visible(),
17589            "Hover popover should be hidden when completion menu is shown"
17590        );
17591    });
17592}
17593
17594#[gpui::test]
17595async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
17596    init_test(cx, |_| {});
17597
17598    let mut cx = EditorLspTestContext::new_rust(
17599        lsp::ServerCapabilities {
17600            completion_provider: Some(lsp::CompletionOptions {
17601                trigger_characters: Some(vec![".".to_string()]),
17602                resolve_provider: Some(true),
17603                ..Default::default()
17604            }),
17605            ..Default::default()
17606        },
17607        cx,
17608    )
17609    .await;
17610
17611    cx.set_state("fn main() { let a = 2ˇ; }");
17612    cx.simulate_keystroke(".");
17613
17614    let unresolved_item_1 = lsp::CompletionItem {
17615        label: "id".to_string(),
17616        filter_text: Some("id".to_string()),
17617        detail: None,
17618        documentation: None,
17619        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17620            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17621            new_text: ".id".to_string(),
17622        })),
17623        ..lsp::CompletionItem::default()
17624    };
17625    let resolved_item_1 = lsp::CompletionItem {
17626        additional_text_edits: Some(vec![lsp::TextEdit {
17627            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
17628            new_text: "!!".to_string(),
17629        }]),
17630        ..unresolved_item_1.clone()
17631    };
17632    let unresolved_item_2 = lsp::CompletionItem {
17633        label: "other".to_string(),
17634        filter_text: Some("other".to_string()),
17635        detail: None,
17636        documentation: None,
17637        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17638            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17639            new_text: ".other".to_string(),
17640        })),
17641        ..lsp::CompletionItem::default()
17642    };
17643    let resolved_item_2 = lsp::CompletionItem {
17644        additional_text_edits: Some(vec![lsp::TextEdit {
17645            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
17646            new_text: "??".to_string(),
17647        }]),
17648        ..unresolved_item_2.clone()
17649    };
17650
17651    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
17652    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
17653    cx.lsp
17654        .server
17655        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
17656            let unresolved_item_1 = unresolved_item_1.clone();
17657            let resolved_item_1 = resolved_item_1.clone();
17658            let unresolved_item_2 = unresolved_item_2.clone();
17659            let resolved_item_2 = resolved_item_2.clone();
17660            let resolve_requests_1 = resolve_requests_1.clone();
17661            let resolve_requests_2 = resolve_requests_2.clone();
17662            move |unresolved_request, _| {
17663                let unresolved_item_1 = unresolved_item_1.clone();
17664                let resolved_item_1 = resolved_item_1.clone();
17665                let unresolved_item_2 = unresolved_item_2.clone();
17666                let resolved_item_2 = resolved_item_2.clone();
17667                let resolve_requests_1 = resolve_requests_1.clone();
17668                let resolve_requests_2 = resolve_requests_2.clone();
17669                async move {
17670                    if unresolved_request == unresolved_item_1 {
17671                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
17672                        Ok(resolved_item_1.clone())
17673                    } else if unresolved_request == unresolved_item_2 {
17674                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
17675                        Ok(resolved_item_2.clone())
17676                    } else {
17677                        panic!("Unexpected completion item {unresolved_request:?}")
17678                    }
17679                }
17680            }
17681        })
17682        .detach();
17683
17684    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17685        let unresolved_item_1 = unresolved_item_1.clone();
17686        let unresolved_item_2 = unresolved_item_2.clone();
17687        async move {
17688            Ok(Some(lsp::CompletionResponse::Array(vec![
17689                unresolved_item_1,
17690                unresolved_item_2,
17691            ])))
17692        }
17693    })
17694    .next()
17695    .await;
17696
17697    cx.condition(|editor, _| editor.context_menu_visible())
17698        .await;
17699    cx.update_editor(|editor, _, _| {
17700        let context_menu = editor.context_menu.borrow_mut();
17701        let context_menu = context_menu
17702            .as_ref()
17703            .expect("Should have the context menu deployed");
17704        match context_menu {
17705            CodeContextMenu::Completions(completions_menu) => {
17706                let completions = completions_menu.completions.borrow_mut();
17707                assert_eq!(
17708                    completions
17709                        .iter()
17710                        .map(|completion| &completion.label.text)
17711                        .collect::<Vec<_>>(),
17712                    vec!["id", "other"]
17713                )
17714            }
17715            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17716        }
17717    });
17718    cx.run_until_parked();
17719
17720    cx.update_editor(|editor, window, cx| {
17721        editor.context_menu_next(&ContextMenuNext, window, cx);
17722    });
17723    cx.run_until_parked();
17724    cx.update_editor(|editor, window, cx| {
17725        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
17726    });
17727    cx.run_until_parked();
17728    cx.update_editor(|editor, window, cx| {
17729        editor.context_menu_next(&ContextMenuNext, window, cx);
17730    });
17731    cx.run_until_parked();
17732    cx.update_editor(|editor, window, cx| {
17733        editor
17734            .compose_completion(&ComposeCompletion::default(), window, cx)
17735            .expect("No task returned")
17736    })
17737    .await
17738    .expect("Completion failed");
17739    cx.run_until_parked();
17740
17741    cx.update_editor(|editor, _, cx| {
17742        assert_eq!(
17743            resolve_requests_1.load(atomic::Ordering::Acquire),
17744            1,
17745            "Should always resolve once despite multiple selections"
17746        );
17747        assert_eq!(
17748            resolve_requests_2.load(atomic::Ordering::Acquire),
17749            1,
17750            "Should always resolve once after multiple selections and applying the completion"
17751        );
17752        assert_eq!(
17753            editor.text(cx),
17754            "fn main() { let a = ??.other; }",
17755            "Should use resolved data when applying the completion"
17756        );
17757    });
17758}
17759
17760#[gpui::test]
17761async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
17762    init_test(cx, |_| {});
17763
17764    let item_0 = lsp::CompletionItem {
17765        label: "abs".into(),
17766        insert_text: Some("abs".into()),
17767        data: Some(json!({ "very": "special"})),
17768        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
17769        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
17770            lsp::InsertReplaceEdit {
17771                new_text: "abs".to_string(),
17772                insert: lsp::Range::default(),
17773                replace: lsp::Range::default(),
17774            },
17775        )),
17776        ..lsp::CompletionItem::default()
17777    };
17778    let items = iter::once(item_0.clone())
17779        .chain((11..51).map(|i| lsp::CompletionItem {
17780            label: format!("item_{}", i),
17781            insert_text: Some(format!("item_{}", i)),
17782            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
17783            ..lsp::CompletionItem::default()
17784        }))
17785        .collect::<Vec<_>>();
17786
17787    let default_commit_characters = vec!["?".to_string()];
17788    let default_data = json!({ "default": "data"});
17789    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
17790    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
17791    let default_edit_range = lsp::Range {
17792        start: lsp::Position {
17793            line: 0,
17794            character: 5,
17795        },
17796        end: lsp::Position {
17797            line: 0,
17798            character: 5,
17799        },
17800    };
17801
17802    let mut cx = EditorLspTestContext::new_rust(
17803        lsp::ServerCapabilities {
17804            completion_provider: Some(lsp::CompletionOptions {
17805                trigger_characters: Some(vec![".".to_string()]),
17806                resolve_provider: Some(true),
17807                ..Default::default()
17808            }),
17809            ..Default::default()
17810        },
17811        cx,
17812    )
17813    .await;
17814
17815    cx.set_state("fn main() { let a = 2ˇ; }");
17816    cx.simulate_keystroke(".");
17817
17818    let completion_data = default_data.clone();
17819    let completion_characters = default_commit_characters.clone();
17820    let completion_items = items.clone();
17821    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17822        let default_data = completion_data.clone();
17823        let default_commit_characters = completion_characters.clone();
17824        let items = completion_items.clone();
17825        async move {
17826            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
17827                items,
17828                item_defaults: Some(lsp::CompletionListItemDefaults {
17829                    data: Some(default_data.clone()),
17830                    commit_characters: Some(default_commit_characters.clone()),
17831                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
17832                        default_edit_range,
17833                    )),
17834                    insert_text_format: Some(default_insert_text_format),
17835                    insert_text_mode: Some(default_insert_text_mode),
17836                }),
17837                ..lsp::CompletionList::default()
17838            })))
17839        }
17840    })
17841    .next()
17842    .await;
17843
17844    let resolved_items = Arc::new(Mutex::new(Vec::new()));
17845    cx.lsp
17846        .server
17847        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
17848            let closure_resolved_items = resolved_items.clone();
17849            move |item_to_resolve, _| {
17850                let closure_resolved_items = closure_resolved_items.clone();
17851                async move {
17852                    closure_resolved_items.lock().push(item_to_resolve.clone());
17853                    Ok(item_to_resolve)
17854                }
17855            }
17856        })
17857        .detach();
17858
17859    cx.condition(|editor, _| editor.context_menu_visible())
17860        .await;
17861    cx.run_until_parked();
17862    cx.update_editor(|editor, _, _| {
17863        let menu = editor.context_menu.borrow_mut();
17864        match menu.as_ref().expect("should have the completions menu") {
17865            CodeContextMenu::Completions(completions_menu) => {
17866                assert_eq!(
17867                    completions_menu
17868                        .entries
17869                        .borrow()
17870                        .iter()
17871                        .map(|mat| mat.string.clone())
17872                        .collect::<Vec<String>>(),
17873                    items
17874                        .iter()
17875                        .map(|completion| completion.label.clone())
17876                        .collect::<Vec<String>>()
17877                );
17878            }
17879            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
17880        }
17881    });
17882    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
17883    // with 4 from the end.
17884    assert_eq!(
17885        *resolved_items.lock(),
17886        [&items[0..16], &items[items.len() - 4..items.len()]]
17887            .concat()
17888            .iter()
17889            .cloned()
17890            .map(|mut item| {
17891                if item.data.is_none() {
17892                    item.data = Some(default_data.clone());
17893                }
17894                item
17895            })
17896            .collect::<Vec<lsp::CompletionItem>>(),
17897        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
17898    );
17899    resolved_items.lock().clear();
17900
17901    cx.update_editor(|editor, window, cx| {
17902        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
17903    });
17904    cx.run_until_parked();
17905    // Completions that have already been resolved are skipped.
17906    assert_eq!(
17907        *resolved_items.lock(),
17908        items[items.len() - 17..items.len() - 4]
17909            .iter()
17910            .cloned()
17911            .map(|mut item| {
17912                if item.data.is_none() {
17913                    item.data = Some(default_data.clone());
17914                }
17915                item
17916            })
17917            .collect::<Vec<lsp::CompletionItem>>()
17918    );
17919    resolved_items.lock().clear();
17920}
17921
17922#[gpui::test]
17923async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
17924    init_test(cx, |_| {});
17925
17926    let mut cx = EditorLspTestContext::new(
17927        Language::new(
17928            LanguageConfig {
17929                matcher: LanguageMatcher {
17930                    path_suffixes: vec!["jsx".into()],
17931                    ..Default::default()
17932                },
17933                overrides: [(
17934                    "element".into(),
17935                    LanguageConfigOverride {
17936                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
17937                        ..Default::default()
17938                    },
17939                )]
17940                .into_iter()
17941                .collect(),
17942                ..Default::default()
17943            },
17944            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
17945        )
17946        .with_override_query("(jsx_self_closing_element) @element")
17947        .unwrap(),
17948        lsp::ServerCapabilities {
17949            completion_provider: Some(lsp::CompletionOptions {
17950                trigger_characters: Some(vec![":".to_string()]),
17951                ..Default::default()
17952            }),
17953            ..Default::default()
17954        },
17955        cx,
17956    )
17957    .await;
17958
17959    cx.lsp
17960        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
17961            Ok(Some(lsp::CompletionResponse::Array(vec![
17962                lsp::CompletionItem {
17963                    label: "bg-blue".into(),
17964                    ..Default::default()
17965                },
17966                lsp::CompletionItem {
17967                    label: "bg-red".into(),
17968                    ..Default::default()
17969                },
17970                lsp::CompletionItem {
17971                    label: "bg-yellow".into(),
17972                    ..Default::default()
17973                },
17974            ])))
17975        });
17976
17977    cx.set_state(r#"<p class="bgˇ" />"#);
17978
17979    // Trigger completion when typing a dash, because the dash is an extra
17980    // word character in the 'element' scope, which contains the cursor.
17981    cx.simulate_keystroke("-");
17982    cx.executor().run_until_parked();
17983    cx.update_editor(|editor, _, _| {
17984        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17985        {
17986            assert_eq!(
17987                completion_menu_entries(menu),
17988                &["bg-blue", "bg-red", "bg-yellow"]
17989            );
17990        } else {
17991            panic!("expected completion menu to be open");
17992        }
17993    });
17994
17995    cx.simulate_keystroke("l");
17996    cx.executor().run_until_parked();
17997    cx.update_editor(|editor, _, _| {
17998        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17999        {
18000            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
18001        } else {
18002            panic!("expected completion menu to be open");
18003        }
18004    });
18005
18006    // When filtering completions, consider the character after the '-' to
18007    // be the start of a subword.
18008    cx.set_state(r#"<p class="yelˇ" />"#);
18009    cx.simulate_keystroke("l");
18010    cx.executor().run_until_parked();
18011    cx.update_editor(|editor, _, _| {
18012        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18013        {
18014            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
18015        } else {
18016            panic!("expected completion menu to be open");
18017        }
18018    });
18019}
18020
18021fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
18022    let entries = menu.entries.borrow();
18023    entries.iter().map(|mat| mat.string.clone()).collect()
18024}
18025
18026#[gpui::test]
18027async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
18028    init_test(cx, |settings| {
18029        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
18030            Formatter::Prettier,
18031        )))
18032    });
18033
18034    let fs = FakeFs::new(cx.executor());
18035    fs.insert_file(path!("/file.ts"), Default::default()).await;
18036
18037    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
18038    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18039
18040    language_registry.add(Arc::new(Language::new(
18041        LanguageConfig {
18042            name: "TypeScript".into(),
18043            matcher: LanguageMatcher {
18044                path_suffixes: vec!["ts".to_string()],
18045                ..Default::default()
18046            },
18047            ..Default::default()
18048        },
18049        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
18050    )));
18051    update_test_language_settings(cx, |settings| {
18052        settings.defaults.prettier = Some(PrettierSettings {
18053            allowed: true,
18054            ..PrettierSettings::default()
18055        });
18056    });
18057
18058    let test_plugin = "test_plugin";
18059    let _ = language_registry.register_fake_lsp(
18060        "TypeScript",
18061        FakeLspAdapter {
18062            prettier_plugins: vec![test_plugin],
18063            ..Default::default()
18064        },
18065    );
18066
18067    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
18068    let buffer = project
18069        .update(cx, |project, cx| {
18070            project.open_local_buffer(path!("/file.ts"), cx)
18071        })
18072        .await
18073        .unwrap();
18074
18075    let buffer_text = "one\ntwo\nthree\n";
18076    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
18077    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
18078    editor.update_in(cx, |editor, window, cx| {
18079        editor.set_text(buffer_text, window, cx)
18080    });
18081
18082    editor
18083        .update_in(cx, |editor, window, cx| {
18084            editor.perform_format(
18085                project.clone(),
18086                FormatTrigger::Manual,
18087                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18088                window,
18089                cx,
18090            )
18091        })
18092        .unwrap()
18093        .await;
18094    assert_eq!(
18095        editor.update(cx, |editor, cx| editor.text(cx)),
18096        buffer_text.to_string() + prettier_format_suffix,
18097        "Test prettier formatting was not applied to the original buffer text",
18098    );
18099
18100    update_test_language_settings(cx, |settings| {
18101        settings.defaults.formatter = Some(SelectedFormatter::Auto)
18102    });
18103    let format = editor.update_in(cx, |editor, window, cx| {
18104        editor.perform_format(
18105            project.clone(),
18106            FormatTrigger::Manual,
18107            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18108            window,
18109            cx,
18110        )
18111    });
18112    format.await.unwrap();
18113    assert_eq!(
18114        editor.update(cx, |editor, cx| editor.text(cx)),
18115        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
18116        "Autoformatting (via test prettier) was not applied to the original buffer text",
18117    );
18118}
18119
18120#[gpui::test]
18121async fn test_addition_reverts(cx: &mut TestAppContext) {
18122    init_test(cx, |_| {});
18123    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18124    let base_text = indoc! {r#"
18125        struct Row;
18126        struct Row1;
18127        struct Row2;
18128
18129        struct Row4;
18130        struct Row5;
18131        struct Row6;
18132
18133        struct Row8;
18134        struct Row9;
18135        struct Row10;"#};
18136
18137    // When addition hunks are not adjacent to carets, no hunk revert is performed
18138    assert_hunk_revert(
18139        indoc! {r#"struct Row;
18140                   struct Row1;
18141                   struct Row1.1;
18142                   struct Row1.2;
18143                   struct Row2;ˇ
18144
18145                   struct Row4;
18146                   struct Row5;
18147                   struct Row6;
18148
18149                   struct Row8;
18150                   ˇstruct Row9;
18151                   struct Row9.1;
18152                   struct Row9.2;
18153                   struct Row9.3;
18154                   struct Row10;"#},
18155        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18156        indoc! {r#"struct Row;
18157                   struct Row1;
18158                   struct Row1.1;
18159                   struct Row1.2;
18160                   struct Row2;ˇ
18161
18162                   struct Row4;
18163                   struct Row5;
18164                   struct Row6;
18165
18166                   struct Row8;
18167                   ˇstruct Row9;
18168                   struct Row9.1;
18169                   struct Row9.2;
18170                   struct Row9.3;
18171                   struct Row10;"#},
18172        base_text,
18173        &mut cx,
18174    );
18175    // Same for selections
18176    assert_hunk_revert(
18177        indoc! {r#"struct Row;
18178                   struct Row1;
18179                   struct Row2;
18180                   struct Row2.1;
18181                   struct Row2.2;
18182                   «ˇ
18183                   struct Row4;
18184                   struct» Row5;
18185                   «struct Row6;
18186                   ˇ»
18187                   struct Row9.1;
18188                   struct Row9.2;
18189                   struct Row9.3;
18190                   struct Row8;
18191                   struct Row9;
18192                   struct Row10;"#},
18193        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18194        indoc! {r#"struct Row;
18195                   struct Row1;
18196                   struct Row2;
18197                   struct Row2.1;
18198                   struct Row2.2;
18199                   «ˇ
18200                   struct Row4;
18201                   struct» Row5;
18202                   «struct Row6;
18203                   ˇ»
18204                   struct Row9.1;
18205                   struct Row9.2;
18206                   struct Row9.3;
18207                   struct Row8;
18208                   struct Row9;
18209                   struct Row10;"#},
18210        base_text,
18211        &mut cx,
18212    );
18213
18214    // When carets and selections intersect the addition hunks, those are reverted.
18215    // Adjacent carets got merged.
18216    assert_hunk_revert(
18217        indoc! {r#"struct Row;
18218                   ˇ// something on the top
18219                   struct Row1;
18220                   struct Row2;
18221                   struct Roˇw3.1;
18222                   struct Row2.2;
18223                   struct Row2.3;ˇ
18224
18225                   struct Row4;
18226                   struct ˇRow5.1;
18227                   struct Row5.2;
18228                   struct «Rowˇ»5.3;
18229                   struct Row5;
18230                   struct Row6;
18231                   ˇ
18232                   struct Row9.1;
18233                   struct «Rowˇ»9.2;
18234                   struct «ˇRow»9.3;
18235                   struct Row8;
18236                   struct Row9;
18237                   «ˇ// something on bottom»
18238                   struct Row10;"#},
18239        vec![
18240            DiffHunkStatusKind::Added,
18241            DiffHunkStatusKind::Added,
18242            DiffHunkStatusKind::Added,
18243            DiffHunkStatusKind::Added,
18244            DiffHunkStatusKind::Added,
18245        ],
18246        indoc! {r#"struct Row;
18247                   ˇstruct Row1;
18248                   struct Row2;
18249                   ˇ
18250                   struct Row4;
18251                   ˇstruct Row5;
18252                   struct Row6;
18253                   ˇ
18254                   ˇstruct Row8;
18255                   struct Row9;
18256                   ˇstruct Row10;"#},
18257        base_text,
18258        &mut cx,
18259    );
18260}
18261
18262#[gpui::test]
18263async fn test_modification_reverts(cx: &mut TestAppContext) {
18264    init_test(cx, |_| {});
18265    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18266    let base_text = indoc! {r#"
18267        struct Row;
18268        struct Row1;
18269        struct Row2;
18270
18271        struct Row4;
18272        struct Row5;
18273        struct Row6;
18274
18275        struct Row8;
18276        struct Row9;
18277        struct Row10;"#};
18278
18279    // Modification hunks behave the same as the addition ones.
18280    assert_hunk_revert(
18281        indoc! {r#"struct Row;
18282                   struct Row1;
18283                   struct Row33;
18284                   ˇ
18285                   struct Row4;
18286                   struct Row5;
18287                   struct Row6;
18288                   ˇ
18289                   struct Row99;
18290                   struct Row9;
18291                   struct Row10;"#},
18292        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18293        indoc! {r#"struct Row;
18294                   struct Row1;
18295                   struct Row33;
18296                   ˇ
18297                   struct Row4;
18298                   struct Row5;
18299                   struct Row6;
18300                   ˇ
18301                   struct Row99;
18302                   struct Row9;
18303                   struct Row10;"#},
18304        base_text,
18305        &mut cx,
18306    );
18307    assert_hunk_revert(
18308        indoc! {r#"struct Row;
18309                   struct Row1;
18310                   struct Row33;
18311                   «ˇ
18312                   struct Row4;
18313                   struct» Row5;
18314                   «struct Row6;
18315                   ˇ»
18316                   struct Row99;
18317                   struct Row9;
18318                   struct Row10;"#},
18319        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18320        indoc! {r#"struct Row;
18321                   struct Row1;
18322                   struct Row33;
18323                   «ˇ
18324                   struct Row4;
18325                   struct» Row5;
18326                   «struct Row6;
18327                   ˇ»
18328                   struct Row99;
18329                   struct Row9;
18330                   struct Row10;"#},
18331        base_text,
18332        &mut cx,
18333    );
18334
18335    assert_hunk_revert(
18336        indoc! {r#"ˇstruct Row1.1;
18337                   struct Row1;
18338                   «ˇstr»uct Row22;
18339
18340                   struct ˇRow44;
18341                   struct Row5;
18342                   struct «Rˇ»ow66;ˇ
18343
18344                   «struˇ»ct Row88;
18345                   struct Row9;
18346                   struct Row1011;ˇ"#},
18347        vec![
18348            DiffHunkStatusKind::Modified,
18349            DiffHunkStatusKind::Modified,
18350            DiffHunkStatusKind::Modified,
18351            DiffHunkStatusKind::Modified,
18352            DiffHunkStatusKind::Modified,
18353            DiffHunkStatusKind::Modified,
18354        ],
18355        indoc! {r#"struct Row;
18356                   ˇstruct Row1;
18357                   struct Row2;
18358                   ˇ
18359                   struct Row4;
18360                   ˇstruct Row5;
18361                   struct Row6;
18362                   ˇ
18363                   struct Row8;
18364                   ˇstruct Row9;
18365                   struct Row10;ˇ"#},
18366        base_text,
18367        &mut cx,
18368    );
18369}
18370
18371#[gpui::test]
18372async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
18373    init_test(cx, |_| {});
18374    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18375    let base_text = indoc! {r#"
18376        one
18377
18378        two
18379        three
18380        "#};
18381
18382    cx.set_head_text(base_text);
18383    cx.set_state("\nˇ\n");
18384    cx.executor().run_until_parked();
18385    cx.update_editor(|editor, _window, cx| {
18386        editor.expand_selected_diff_hunks(cx);
18387    });
18388    cx.executor().run_until_parked();
18389    cx.update_editor(|editor, window, cx| {
18390        editor.backspace(&Default::default(), window, cx);
18391    });
18392    cx.run_until_parked();
18393    cx.assert_state_with_diff(
18394        indoc! {r#"
18395
18396        - two
18397        - threeˇ
18398        +
18399        "#}
18400        .to_string(),
18401    );
18402}
18403
18404#[gpui::test]
18405async fn test_deletion_reverts(cx: &mut TestAppContext) {
18406    init_test(cx, |_| {});
18407    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18408    let base_text = indoc! {r#"struct Row;
18409struct Row1;
18410struct Row2;
18411
18412struct Row4;
18413struct Row5;
18414struct Row6;
18415
18416struct Row8;
18417struct Row9;
18418struct Row10;"#};
18419
18420    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
18421    assert_hunk_revert(
18422        indoc! {r#"struct Row;
18423                   struct Row2;
18424
18425                   ˇstruct Row4;
18426                   struct Row5;
18427                   struct Row6;
18428                   ˇ
18429                   struct Row8;
18430                   struct Row10;"#},
18431        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18432        indoc! {r#"struct Row;
18433                   struct Row2;
18434
18435                   ˇstruct Row4;
18436                   struct Row5;
18437                   struct Row6;
18438                   ˇ
18439                   struct Row8;
18440                   struct Row10;"#},
18441        base_text,
18442        &mut cx,
18443    );
18444    assert_hunk_revert(
18445        indoc! {r#"struct Row;
18446                   struct Row2;
18447
18448                   «ˇstruct Row4;
18449                   struct» Row5;
18450                   «struct Row6;
18451                   ˇ»
18452                   struct Row8;
18453                   struct Row10;"#},
18454        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18455        indoc! {r#"struct Row;
18456                   struct Row2;
18457
18458                   «ˇstruct Row4;
18459                   struct» Row5;
18460                   «struct Row6;
18461                   ˇ»
18462                   struct Row8;
18463                   struct Row10;"#},
18464        base_text,
18465        &mut cx,
18466    );
18467
18468    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
18469    assert_hunk_revert(
18470        indoc! {r#"struct Row;
18471                   ˇstruct Row2;
18472
18473                   struct Row4;
18474                   struct Row5;
18475                   struct Row6;
18476
18477                   struct Row8;ˇ
18478                   struct Row10;"#},
18479        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18480        indoc! {r#"struct Row;
18481                   struct Row1;
18482                   ˇstruct Row2;
18483
18484                   struct Row4;
18485                   struct Row5;
18486                   struct Row6;
18487
18488                   struct Row8;ˇ
18489                   struct Row9;
18490                   struct Row10;"#},
18491        base_text,
18492        &mut cx,
18493    );
18494    assert_hunk_revert(
18495        indoc! {r#"struct Row;
18496                   struct Row2«ˇ;
18497                   struct Row4;
18498                   struct» Row5;
18499                   «struct Row6;
18500
18501                   struct Row8;ˇ»
18502                   struct Row10;"#},
18503        vec![
18504            DiffHunkStatusKind::Deleted,
18505            DiffHunkStatusKind::Deleted,
18506            DiffHunkStatusKind::Deleted,
18507        ],
18508        indoc! {r#"struct Row;
18509                   struct Row1;
18510                   struct Row2«ˇ;
18511
18512                   struct Row4;
18513                   struct» Row5;
18514                   «struct Row6;
18515
18516                   struct Row8;ˇ»
18517                   struct Row9;
18518                   struct Row10;"#},
18519        base_text,
18520        &mut cx,
18521    );
18522}
18523
18524#[gpui::test]
18525async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
18526    init_test(cx, |_| {});
18527
18528    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
18529    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
18530    let base_text_3 =
18531        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
18532
18533    let text_1 = edit_first_char_of_every_line(base_text_1);
18534    let text_2 = edit_first_char_of_every_line(base_text_2);
18535    let text_3 = edit_first_char_of_every_line(base_text_3);
18536
18537    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
18538    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
18539    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
18540
18541    let multibuffer = cx.new(|cx| {
18542        let mut multibuffer = MultiBuffer::new(ReadWrite);
18543        multibuffer.push_excerpts(
18544            buffer_1.clone(),
18545            [
18546                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18547                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18548                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18549            ],
18550            cx,
18551        );
18552        multibuffer.push_excerpts(
18553            buffer_2.clone(),
18554            [
18555                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18556                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18557                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18558            ],
18559            cx,
18560        );
18561        multibuffer.push_excerpts(
18562            buffer_3.clone(),
18563            [
18564                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18565                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18566                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18567            ],
18568            cx,
18569        );
18570        multibuffer
18571    });
18572
18573    let fs = FakeFs::new(cx.executor());
18574    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
18575    let (editor, cx) = cx
18576        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
18577    editor.update_in(cx, |editor, _window, cx| {
18578        for (buffer, diff_base) in [
18579            (buffer_1.clone(), base_text_1),
18580            (buffer_2.clone(), base_text_2),
18581            (buffer_3.clone(), base_text_3),
18582        ] {
18583            let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
18584            editor
18585                .buffer
18586                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
18587        }
18588    });
18589    cx.executor().run_until_parked();
18590
18591    editor.update_in(cx, |editor, window, cx| {
18592        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}");
18593        editor.select_all(&SelectAll, window, cx);
18594        editor.git_restore(&Default::default(), window, cx);
18595    });
18596    cx.executor().run_until_parked();
18597
18598    // When all ranges are selected, all buffer hunks are reverted.
18599    editor.update(cx, |editor, cx| {
18600        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");
18601    });
18602    buffer_1.update(cx, |buffer, _| {
18603        assert_eq!(buffer.text(), base_text_1);
18604    });
18605    buffer_2.update(cx, |buffer, _| {
18606        assert_eq!(buffer.text(), base_text_2);
18607    });
18608    buffer_3.update(cx, |buffer, _| {
18609        assert_eq!(buffer.text(), base_text_3);
18610    });
18611
18612    editor.update_in(cx, |editor, window, cx| {
18613        editor.undo(&Default::default(), window, cx);
18614    });
18615
18616    editor.update_in(cx, |editor, window, cx| {
18617        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18618            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
18619        });
18620        editor.git_restore(&Default::default(), window, cx);
18621    });
18622
18623    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
18624    // but not affect buffer_2 and its related excerpts.
18625    editor.update(cx, |editor, cx| {
18626        assert_eq!(
18627            editor.text(cx),
18628            "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}"
18629        );
18630    });
18631    buffer_1.update(cx, |buffer, _| {
18632        assert_eq!(buffer.text(), base_text_1);
18633    });
18634    buffer_2.update(cx, |buffer, _| {
18635        assert_eq!(
18636            buffer.text(),
18637            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
18638        );
18639    });
18640    buffer_3.update(cx, |buffer, _| {
18641        assert_eq!(
18642            buffer.text(),
18643            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
18644        );
18645    });
18646
18647    fn edit_first_char_of_every_line(text: &str) -> String {
18648        text.split('\n')
18649            .map(|line| format!("X{}", &line[1..]))
18650            .collect::<Vec<_>>()
18651            .join("\n")
18652    }
18653}
18654
18655#[gpui::test]
18656async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
18657    init_test(cx, |_| {});
18658
18659    let cols = 4;
18660    let rows = 10;
18661    let sample_text_1 = sample_text(rows, cols, 'a');
18662    assert_eq!(
18663        sample_text_1,
18664        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
18665    );
18666    let sample_text_2 = sample_text(rows, cols, 'l');
18667    assert_eq!(
18668        sample_text_2,
18669        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
18670    );
18671    let sample_text_3 = sample_text(rows, cols, 'v');
18672    assert_eq!(
18673        sample_text_3,
18674        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
18675    );
18676
18677    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
18678    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
18679    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
18680
18681    let multi_buffer = cx.new(|cx| {
18682        let mut multibuffer = MultiBuffer::new(ReadWrite);
18683        multibuffer.push_excerpts(
18684            buffer_1.clone(),
18685            [
18686                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18687                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18688                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18689            ],
18690            cx,
18691        );
18692        multibuffer.push_excerpts(
18693            buffer_2.clone(),
18694            [
18695                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18696                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18697                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18698            ],
18699            cx,
18700        );
18701        multibuffer.push_excerpts(
18702            buffer_3.clone(),
18703            [
18704                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18705                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18706                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18707            ],
18708            cx,
18709        );
18710        multibuffer
18711    });
18712
18713    let fs = FakeFs::new(cx.executor());
18714    fs.insert_tree(
18715        "/a",
18716        json!({
18717            "main.rs": sample_text_1,
18718            "other.rs": sample_text_2,
18719            "lib.rs": sample_text_3,
18720        }),
18721    )
18722    .await;
18723    let project = Project::test(fs, ["/a".as_ref()], cx).await;
18724    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18725    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18726    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
18727        Editor::new(
18728            EditorMode::full(),
18729            multi_buffer,
18730            Some(project.clone()),
18731            window,
18732            cx,
18733        )
18734    });
18735    let multibuffer_item_id = workspace
18736        .update(cx, |workspace, window, cx| {
18737            assert!(
18738                workspace.active_item(cx).is_none(),
18739                "active item should be None before the first item is added"
18740            );
18741            workspace.add_item_to_active_pane(
18742                Box::new(multi_buffer_editor.clone()),
18743                None,
18744                true,
18745                window,
18746                cx,
18747            );
18748            let active_item = workspace
18749                .active_item(cx)
18750                .expect("should have an active item after adding the multi buffer");
18751            assert!(
18752                !active_item.is_singleton(cx),
18753                "A multi buffer was expected to active after adding"
18754            );
18755            active_item.item_id()
18756        })
18757        .unwrap();
18758    cx.executor().run_until_parked();
18759
18760    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18761        editor.change_selections(
18762            SelectionEffects::scroll(Autoscroll::Next),
18763            window,
18764            cx,
18765            |s| s.select_ranges(Some(1..2)),
18766        );
18767        editor.open_excerpts(&OpenExcerpts, window, cx);
18768    });
18769    cx.executor().run_until_parked();
18770    let first_item_id = workspace
18771        .update(cx, |workspace, window, cx| {
18772            let active_item = workspace
18773                .active_item(cx)
18774                .expect("should have an active item after navigating into the 1st buffer");
18775            let first_item_id = active_item.item_id();
18776            assert_ne!(
18777                first_item_id, multibuffer_item_id,
18778                "Should navigate into the 1st buffer and activate it"
18779            );
18780            assert!(
18781                active_item.is_singleton(cx),
18782                "New active item should be a singleton buffer"
18783            );
18784            assert_eq!(
18785                active_item
18786                    .act_as::<Editor>(cx)
18787                    .expect("should have navigated into an editor for the 1st buffer")
18788                    .read(cx)
18789                    .text(cx),
18790                sample_text_1
18791            );
18792
18793            workspace
18794                .go_back(workspace.active_pane().downgrade(), window, cx)
18795                .detach_and_log_err(cx);
18796
18797            first_item_id
18798        })
18799        .unwrap();
18800    cx.executor().run_until_parked();
18801    workspace
18802        .update(cx, |workspace, _, cx| {
18803            let active_item = workspace
18804                .active_item(cx)
18805                .expect("should have an active item after navigating back");
18806            assert_eq!(
18807                active_item.item_id(),
18808                multibuffer_item_id,
18809                "Should navigate back to the multi buffer"
18810            );
18811            assert!(!active_item.is_singleton(cx));
18812        })
18813        .unwrap();
18814
18815    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18816        editor.change_selections(
18817            SelectionEffects::scroll(Autoscroll::Next),
18818            window,
18819            cx,
18820            |s| s.select_ranges(Some(39..40)),
18821        );
18822        editor.open_excerpts(&OpenExcerpts, window, cx);
18823    });
18824    cx.executor().run_until_parked();
18825    let second_item_id = workspace
18826        .update(cx, |workspace, window, cx| {
18827            let active_item = workspace
18828                .active_item(cx)
18829                .expect("should have an active item after navigating into the 2nd buffer");
18830            let second_item_id = active_item.item_id();
18831            assert_ne!(
18832                second_item_id, multibuffer_item_id,
18833                "Should navigate away from the multibuffer"
18834            );
18835            assert_ne!(
18836                second_item_id, first_item_id,
18837                "Should navigate into the 2nd buffer and activate it"
18838            );
18839            assert!(
18840                active_item.is_singleton(cx),
18841                "New active item should be a singleton buffer"
18842            );
18843            assert_eq!(
18844                active_item
18845                    .act_as::<Editor>(cx)
18846                    .expect("should have navigated into an editor")
18847                    .read(cx)
18848                    .text(cx),
18849                sample_text_2
18850            );
18851
18852            workspace
18853                .go_back(workspace.active_pane().downgrade(), window, cx)
18854                .detach_and_log_err(cx);
18855
18856            second_item_id
18857        })
18858        .unwrap();
18859    cx.executor().run_until_parked();
18860    workspace
18861        .update(cx, |workspace, _, cx| {
18862            let active_item = workspace
18863                .active_item(cx)
18864                .expect("should have an active item after navigating back from the 2nd buffer");
18865            assert_eq!(
18866                active_item.item_id(),
18867                multibuffer_item_id,
18868                "Should navigate back from the 2nd buffer to the multi buffer"
18869            );
18870            assert!(!active_item.is_singleton(cx));
18871        })
18872        .unwrap();
18873
18874    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18875        editor.change_selections(
18876            SelectionEffects::scroll(Autoscroll::Next),
18877            window,
18878            cx,
18879            |s| s.select_ranges(Some(70..70)),
18880        );
18881        editor.open_excerpts(&OpenExcerpts, window, cx);
18882    });
18883    cx.executor().run_until_parked();
18884    workspace
18885        .update(cx, |workspace, window, cx| {
18886            let active_item = workspace
18887                .active_item(cx)
18888                .expect("should have an active item after navigating into the 3rd buffer");
18889            let third_item_id = active_item.item_id();
18890            assert_ne!(
18891                third_item_id, multibuffer_item_id,
18892                "Should navigate into the 3rd buffer and activate it"
18893            );
18894            assert_ne!(third_item_id, first_item_id);
18895            assert_ne!(third_item_id, second_item_id);
18896            assert!(
18897                active_item.is_singleton(cx),
18898                "New active item should be a singleton buffer"
18899            );
18900            assert_eq!(
18901                active_item
18902                    .act_as::<Editor>(cx)
18903                    .expect("should have navigated into an editor")
18904                    .read(cx)
18905                    .text(cx),
18906                sample_text_3
18907            );
18908
18909            workspace
18910                .go_back(workspace.active_pane().downgrade(), window, cx)
18911                .detach_and_log_err(cx);
18912        })
18913        .unwrap();
18914    cx.executor().run_until_parked();
18915    workspace
18916        .update(cx, |workspace, _, cx| {
18917            let active_item = workspace
18918                .active_item(cx)
18919                .expect("should have an active item after navigating back from the 3rd buffer");
18920            assert_eq!(
18921                active_item.item_id(),
18922                multibuffer_item_id,
18923                "Should navigate back from the 3rd buffer to the multi buffer"
18924            );
18925            assert!(!active_item.is_singleton(cx));
18926        })
18927        .unwrap();
18928}
18929
18930#[gpui::test]
18931async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
18932    init_test(cx, |_| {});
18933
18934    let mut cx = EditorTestContext::new(cx).await;
18935
18936    let diff_base = r#"
18937        use some::mod;
18938
18939        const A: u32 = 42;
18940
18941        fn main() {
18942            println!("hello");
18943
18944            println!("world");
18945        }
18946        "#
18947    .unindent();
18948
18949    cx.set_state(
18950        &r#"
18951        use some::modified;
18952
18953        ˇ
18954        fn main() {
18955            println!("hello there");
18956
18957            println!("around the");
18958            println!("world");
18959        }
18960        "#
18961        .unindent(),
18962    );
18963
18964    cx.set_head_text(&diff_base);
18965    executor.run_until_parked();
18966
18967    cx.update_editor(|editor, window, cx| {
18968        editor.go_to_next_hunk(&GoToHunk, window, cx);
18969        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
18970    });
18971    executor.run_until_parked();
18972    cx.assert_state_with_diff(
18973        r#"
18974          use some::modified;
18975
18976
18977          fn main() {
18978        -     println!("hello");
18979        + ˇ    println!("hello there");
18980
18981              println!("around the");
18982              println!("world");
18983          }
18984        "#
18985        .unindent(),
18986    );
18987
18988    cx.update_editor(|editor, window, cx| {
18989        for _ in 0..2 {
18990            editor.go_to_next_hunk(&GoToHunk, window, cx);
18991            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
18992        }
18993    });
18994    executor.run_until_parked();
18995    cx.assert_state_with_diff(
18996        r#"
18997        - use some::mod;
18998        + ˇuse some::modified;
18999
19000
19001          fn main() {
19002        -     println!("hello");
19003        +     println!("hello there");
19004
19005        +     println!("around the");
19006              println!("world");
19007          }
19008        "#
19009        .unindent(),
19010    );
19011
19012    cx.update_editor(|editor, window, cx| {
19013        editor.go_to_next_hunk(&GoToHunk, window, cx);
19014        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19015    });
19016    executor.run_until_parked();
19017    cx.assert_state_with_diff(
19018        r#"
19019        - use some::mod;
19020        + use some::modified;
19021
19022        - const A: u32 = 42;
19023          ˇ
19024          fn main() {
19025        -     println!("hello");
19026        +     println!("hello there");
19027
19028        +     println!("around the");
19029              println!("world");
19030          }
19031        "#
19032        .unindent(),
19033    );
19034
19035    cx.update_editor(|editor, window, cx| {
19036        editor.cancel(&Cancel, window, cx);
19037    });
19038
19039    cx.assert_state_with_diff(
19040        r#"
19041          use some::modified;
19042
19043          ˇ
19044          fn main() {
19045              println!("hello there");
19046
19047              println!("around the");
19048              println!("world");
19049          }
19050        "#
19051        .unindent(),
19052    );
19053}
19054
19055#[gpui::test]
19056async fn test_diff_base_change_with_expanded_diff_hunks(
19057    executor: BackgroundExecutor,
19058    cx: &mut TestAppContext,
19059) {
19060    init_test(cx, |_| {});
19061
19062    let mut cx = EditorTestContext::new(cx).await;
19063
19064    let diff_base = r#"
19065        use some::mod1;
19066        use some::mod2;
19067
19068        const A: u32 = 42;
19069        const B: u32 = 42;
19070        const C: u32 = 42;
19071
19072        fn main() {
19073            println!("hello");
19074
19075            println!("world");
19076        }
19077        "#
19078    .unindent();
19079
19080    cx.set_state(
19081        &r#"
19082        use some::mod2;
19083
19084        const A: u32 = 42;
19085        const C: u32 = 42;
19086
19087        fn main(ˇ) {
19088            //println!("hello");
19089
19090            println!("world");
19091            //
19092            //
19093        }
19094        "#
19095        .unindent(),
19096    );
19097
19098    cx.set_head_text(&diff_base);
19099    executor.run_until_parked();
19100
19101    cx.update_editor(|editor, window, cx| {
19102        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19103    });
19104    executor.run_until_parked();
19105    cx.assert_state_with_diff(
19106        r#"
19107        - use some::mod1;
19108          use some::mod2;
19109
19110          const A: u32 = 42;
19111        - const B: u32 = 42;
19112          const C: u32 = 42;
19113
19114          fn main(ˇ) {
19115        -     println!("hello");
19116        +     //println!("hello");
19117
19118              println!("world");
19119        +     //
19120        +     //
19121          }
19122        "#
19123        .unindent(),
19124    );
19125
19126    cx.set_head_text("new diff base!");
19127    executor.run_until_parked();
19128    cx.assert_state_with_diff(
19129        r#"
19130        - new diff base!
19131        + use some::mod2;
19132        +
19133        + const A: u32 = 42;
19134        + const C: u32 = 42;
19135        +
19136        + fn main(ˇ) {
19137        +     //println!("hello");
19138        +
19139        +     println!("world");
19140        +     //
19141        +     //
19142        + }
19143        "#
19144        .unindent(),
19145    );
19146}
19147
19148#[gpui::test]
19149async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
19150    init_test(cx, |_| {});
19151
19152    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19153    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19154    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19155    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19156    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
19157    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
19158
19159    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
19160    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
19161    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
19162
19163    let multi_buffer = cx.new(|cx| {
19164        let mut multibuffer = MultiBuffer::new(ReadWrite);
19165        multibuffer.push_excerpts(
19166            buffer_1.clone(),
19167            [
19168                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19169                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19170                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19171            ],
19172            cx,
19173        );
19174        multibuffer.push_excerpts(
19175            buffer_2.clone(),
19176            [
19177                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19178                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19179                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19180            ],
19181            cx,
19182        );
19183        multibuffer.push_excerpts(
19184            buffer_3.clone(),
19185            [
19186                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19187                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19188                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19189            ],
19190            cx,
19191        );
19192        multibuffer
19193    });
19194
19195    let editor =
19196        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19197    editor
19198        .update(cx, |editor, _window, cx| {
19199            for (buffer, diff_base) in [
19200                (buffer_1.clone(), file_1_old),
19201                (buffer_2.clone(), file_2_old),
19202                (buffer_3.clone(), file_3_old),
19203            ] {
19204                let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
19205                editor
19206                    .buffer
19207                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
19208            }
19209        })
19210        .unwrap();
19211
19212    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19213    cx.run_until_parked();
19214
19215    cx.assert_editor_state(
19216        &"
19217            ˇaaa
19218            ccc
19219            ddd
19220
19221            ggg
19222            hhh
19223
19224
19225            lll
19226            mmm
19227            NNN
19228
19229            qqq
19230            rrr
19231
19232            uuu
19233            111
19234            222
19235            333
19236
19237            666
19238            777
19239
19240            000
19241            !!!"
19242        .unindent(),
19243    );
19244
19245    cx.update_editor(|editor, window, cx| {
19246        editor.select_all(&SelectAll, window, cx);
19247        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19248    });
19249    cx.executor().run_until_parked();
19250
19251    cx.assert_state_with_diff(
19252        "
19253            «aaa
19254          - bbb
19255            ccc
19256            ddd
19257
19258            ggg
19259            hhh
19260
19261
19262            lll
19263            mmm
19264          - nnn
19265          + NNN
19266
19267            qqq
19268            rrr
19269
19270            uuu
19271            111
19272            222
19273            333
19274
19275          + 666
19276            777
19277
19278            000
19279            !!!ˇ»"
19280            .unindent(),
19281    );
19282}
19283
19284#[gpui::test]
19285async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
19286    init_test(cx, |_| {});
19287
19288    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
19289    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
19290
19291    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
19292    let multi_buffer = cx.new(|cx| {
19293        let mut multibuffer = MultiBuffer::new(ReadWrite);
19294        multibuffer.push_excerpts(
19295            buffer.clone(),
19296            [
19297                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
19298                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
19299                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
19300            ],
19301            cx,
19302        );
19303        multibuffer
19304    });
19305
19306    let editor =
19307        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19308    editor
19309        .update(cx, |editor, _window, cx| {
19310            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
19311            editor
19312                .buffer
19313                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
19314        })
19315        .unwrap();
19316
19317    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19318    cx.run_until_parked();
19319
19320    cx.update_editor(|editor, window, cx| {
19321        editor.expand_all_diff_hunks(&Default::default(), window, cx)
19322    });
19323    cx.executor().run_until_parked();
19324
19325    // When the start of a hunk coincides with the start of its excerpt,
19326    // the hunk is expanded. When the start of a hunk is earlier than
19327    // the start of its excerpt, the hunk is not expanded.
19328    cx.assert_state_with_diff(
19329        "
19330            ˇaaa
19331          - bbb
19332          + BBB
19333
19334          - ddd
19335          - eee
19336          + DDD
19337          + EEE
19338            fff
19339
19340            iii
19341        "
19342        .unindent(),
19343    );
19344}
19345
19346#[gpui::test]
19347async fn test_edits_around_expanded_insertion_hunks(
19348    executor: BackgroundExecutor,
19349    cx: &mut TestAppContext,
19350) {
19351    init_test(cx, |_| {});
19352
19353    let mut cx = EditorTestContext::new(cx).await;
19354
19355    let diff_base = r#"
19356        use some::mod1;
19357        use some::mod2;
19358
19359        const A: u32 = 42;
19360
19361        fn main() {
19362            println!("hello");
19363
19364            println!("world");
19365        }
19366        "#
19367    .unindent();
19368    executor.run_until_parked();
19369    cx.set_state(
19370        &r#"
19371        use some::mod1;
19372        use some::mod2;
19373
19374        const A: u32 = 42;
19375        const B: u32 = 42;
19376        const C: u32 = 42;
19377        ˇ
19378
19379        fn main() {
19380            println!("hello");
19381
19382            println!("world");
19383        }
19384        "#
19385        .unindent(),
19386    );
19387
19388    cx.set_head_text(&diff_base);
19389    executor.run_until_parked();
19390
19391    cx.update_editor(|editor, window, cx| {
19392        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19393    });
19394    executor.run_until_parked();
19395
19396    cx.assert_state_with_diff(
19397        r#"
19398        use some::mod1;
19399        use some::mod2;
19400
19401        const A: u32 = 42;
19402      + const B: u32 = 42;
19403      + const C: u32 = 42;
19404      + ˇ
19405
19406        fn main() {
19407            println!("hello");
19408
19409            println!("world");
19410        }
19411      "#
19412        .unindent(),
19413    );
19414
19415    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
19416    executor.run_until_parked();
19417
19418    cx.assert_state_with_diff(
19419        r#"
19420        use some::mod1;
19421        use some::mod2;
19422
19423        const A: u32 = 42;
19424      + const B: u32 = 42;
19425      + const C: u32 = 42;
19426      + const D: u32 = 42;
19427      + ˇ
19428
19429        fn main() {
19430            println!("hello");
19431
19432            println!("world");
19433        }
19434      "#
19435        .unindent(),
19436    );
19437
19438    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
19439    executor.run_until_parked();
19440
19441    cx.assert_state_with_diff(
19442        r#"
19443        use some::mod1;
19444        use some::mod2;
19445
19446        const A: u32 = 42;
19447      + const B: u32 = 42;
19448      + const C: u32 = 42;
19449      + const D: u32 = 42;
19450      + const E: u32 = 42;
19451      + ˇ
19452
19453        fn main() {
19454            println!("hello");
19455
19456            println!("world");
19457        }
19458      "#
19459        .unindent(),
19460    );
19461
19462    cx.update_editor(|editor, window, cx| {
19463        editor.delete_line(&DeleteLine, window, cx);
19464    });
19465    executor.run_until_parked();
19466
19467    cx.assert_state_with_diff(
19468        r#"
19469        use some::mod1;
19470        use some::mod2;
19471
19472        const A: u32 = 42;
19473      + const B: u32 = 42;
19474      + const C: u32 = 42;
19475      + const D: u32 = 42;
19476      + const E: u32 = 42;
19477        ˇ
19478        fn main() {
19479            println!("hello");
19480
19481            println!("world");
19482        }
19483      "#
19484        .unindent(),
19485    );
19486
19487    cx.update_editor(|editor, window, cx| {
19488        editor.move_up(&MoveUp, window, cx);
19489        editor.delete_line(&DeleteLine, window, cx);
19490        editor.move_up(&MoveUp, window, cx);
19491        editor.delete_line(&DeleteLine, window, cx);
19492        editor.move_up(&MoveUp, window, cx);
19493        editor.delete_line(&DeleteLine, window, cx);
19494    });
19495    executor.run_until_parked();
19496    cx.assert_state_with_diff(
19497        r#"
19498        use some::mod1;
19499        use some::mod2;
19500
19501        const A: u32 = 42;
19502      + const B: u32 = 42;
19503        ˇ
19504        fn main() {
19505            println!("hello");
19506
19507            println!("world");
19508        }
19509      "#
19510        .unindent(),
19511    );
19512
19513    cx.update_editor(|editor, window, cx| {
19514        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
19515        editor.delete_line(&DeleteLine, window, cx);
19516    });
19517    executor.run_until_parked();
19518    cx.assert_state_with_diff(
19519        r#"
19520        ˇ
19521        fn main() {
19522            println!("hello");
19523
19524            println!("world");
19525        }
19526      "#
19527        .unindent(),
19528    );
19529}
19530
19531#[gpui::test]
19532async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
19533    init_test(cx, |_| {});
19534
19535    let mut cx = EditorTestContext::new(cx).await;
19536    cx.set_head_text(indoc! { "
19537        one
19538        two
19539        three
19540        four
19541        five
19542        "
19543    });
19544    cx.set_state(indoc! { "
19545        one
19546        ˇthree
19547        five
19548    "});
19549    cx.run_until_parked();
19550    cx.update_editor(|editor, window, cx| {
19551        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19552    });
19553    cx.assert_state_with_diff(
19554        indoc! { "
19555        one
19556      - two
19557        ˇthree
19558      - four
19559        five
19560    "}
19561        .to_string(),
19562    );
19563    cx.update_editor(|editor, window, cx| {
19564        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19565    });
19566
19567    cx.assert_state_with_diff(
19568        indoc! { "
19569        one
19570        ˇthree
19571        five
19572    "}
19573        .to_string(),
19574    );
19575
19576    cx.set_state(indoc! { "
19577        one
19578        ˇTWO
19579        three
19580        four
19581        five
19582    "});
19583    cx.run_until_parked();
19584    cx.update_editor(|editor, window, cx| {
19585        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19586    });
19587
19588    cx.assert_state_with_diff(
19589        indoc! { "
19590            one
19591          - two
19592          + ˇTWO
19593            three
19594            four
19595            five
19596        "}
19597        .to_string(),
19598    );
19599    cx.update_editor(|editor, window, cx| {
19600        editor.move_up(&Default::default(), window, cx);
19601        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19602    });
19603    cx.assert_state_with_diff(
19604        indoc! { "
19605            one
19606            ˇTWO
19607            three
19608            four
19609            five
19610        "}
19611        .to_string(),
19612    );
19613}
19614
19615#[gpui::test]
19616async fn test_edits_around_expanded_deletion_hunks(
19617    executor: BackgroundExecutor,
19618    cx: &mut TestAppContext,
19619) {
19620    init_test(cx, |_| {});
19621
19622    let mut cx = EditorTestContext::new(cx).await;
19623
19624    let diff_base = r#"
19625        use some::mod1;
19626        use some::mod2;
19627
19628        const A: u32 = 42;
19629        const B: u32 = 42;
19630        const C: u32 = 42;
19631
19632
19633        fn main() {
19634            println!("hello");
19635
19636            println!("world");
19637        }
19638    "#
19639    .unindent();
19640    executor.run_until_parked();
19641    cx.set_state(
19642        &r#"
19643        use some::mod1;
19644        use some::mod2;
19645
19646        ˇconst B: u32 = 42;
19647        const C: u32 = 42;
19648
19649
19650        fn main() {
19651            println!("hello");
19652
19653            println!("world");
19654        }
19655        "#
19656        .unindent(),
19657    );
19658
19659    cx.set_head_text(&diff_base);
19660    executor.run_until_parked();
19661
19662    cx.update_editor(|editor, window, cx| {
19663        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19664    });
19665    executor.run_until_parked();
19666
19667    cx.assert_state_with_diff(
19668        r#"
19669        use some::mod1;
19670        use some::mod2;
19671
19672      - const A: u32 = 42;
19673        ˇconst B: u32 = 42;
19674        const C: u32 = 42;
19675
19676
19677        fn main() {
19678            println!("hello");
19679
19680            println!("world");
19681        }
19682      "#
19683        .unindent(),
19684    );
19685
19686    cx.update_editor(|editor, window, cx| {
19687        editor.delete_line(&DeleteLine, window, cx);
19688    });
19689    executor.run_until_parked();
19690    cx.assert_state_with_diff(
19691        r#"
19692        use some::mod1;
19693        use some::mod2;
19694
19695      - const A: u32 = 42;
19696      - const B: u32 = 42;
19697        ˇconst C: u32 = 42;
19698
19699
19700        fn main() {
19701            println!("hello");
19702
19703            println!("world");
19704        }
19705      "#
19706        .unindent(),
19707    );
19708
19709    cx.update_editor(|editor, window, cx| {
19710        editor.delete_line(&DeleteLine, window, cx);
19711    });
19712    executor.run_until_parked();
19713    cx.assert_state_with_diff(
19714        r#"
19715        use some::mod1;
19716        use some::mod2;
19717
19718      - const A: u32 = 42;
19719      - const B: u32 = 42;
19720      - const C: u32 = 42;
19721        ˇ
19722
19723        fn main() {
19724            println!("hello");
19725
19726            println!("world");
19727        }
19728      "#
19729        .unindent(),
19730    );
19731
19732    cx.update_editor(|editor, window, cx| {
19733        editor.handle_input("replacement", window, cx);
19734    });
19735    executor.run_until_parked();
19736    cx.assert_state_with_diff(
19737        r#"
19738        use some::mod1;
19739        use some::mod2;
19740
19741      - const A: u32 = 42;
19742      - const B: u32 = 42;
19743      - const C: u32 = 42;
19744      -
19745      + replacementˇ
19746
19747        fn main() {
19748            println!("hello");
19749
19750            println!("world");
19751        }
19752      "#
19753        .unindent(),
19754    );
19755}
19756
19757#[gpui::test]
19758async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19759    init_test(cx, |_| {});
19760
19761    let mut cx = EditorTestContext::new(cx).await;
19762
19763    let base_text = r#"
19764        one
19765        two
19766        three
19767        four
19768        five
19769    "#
19770    .unindent();
19771    executor.run_until_parked();
19772    cx.set_state(
19773        &r#"
19774        one
19775        two
19776        fˇour
19777        five
19778        "#
19779        .unindent(),
19780    );
19781
19782    cx.set_head_text(&base_text);
19783    executor.run_until_parked();
19784
19785    cx.update_editor(|editor, window, cx| {
19786        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19787    });
19788    executor.run_until_parked();
19789
19790    cx.assert_state_with_diff(
19791        r#"
19792          one
19793          two
19794        - three
19795          fˇour
19796          five
19797        "#
19798        .unindent(),
19799    );
19800
19801    cx.update_editor(|editor, window, cx| {
19802        editor.backspace(&Backspace, window, cx);
19803        editor.backspace(&Backspace, window, cx);
19804    });
19805    executor.run_until_parked();
19806    cx.assert_state_with_diff(
19807        r#"
19808          one
19809          two
19810        - threeˇ
19811        - four
19812        + our
19813          five
19814        "#
19815        .unindent(),
19816    );
19817}
19818
19819#[gpui::test]
19820async fn test_edit_after_expanded_modification_hunk(
19821    executor: BackgroundExecutor,
19822    cx: &mut TestAppContext,
19823) {
19824    init_test(cx, |_| {});
19825
19826    let mut cx = EditorTestContext::new(cx).await;
19827
19828    let diff_base = r#"
19829        use some::mod1;
19830        use some::mod2;
19831
19832        const A: u32 = 42;
19833        const B: u32 = 42;
19834        const C: u32 = 42;
19835        const D: u32 = 42;
19836
19837
19838        fn main() {
19839            println!("hello");
19840
19841            println!("world");
19842        }"#
19843    .unindent();
19844
19845    cx.set_state(
19846        &r#"
19847        use some::mod1;
19848        use some::mod2;
19849
19850        const A: u32 = 42;
19851        const B: u32 = 42;
19852        const C: u32 = 43ˇ
19853        const D: u32 = 42;
19854
19855
19856        fn main() {
19857            println!("hello");
19858
19859            println!("world");
19860        }"#
19861        .unindent(),
19862    );
19863
19864    cx.set_head_text(&diff_base);
19865    executor.run_until_parked();
19866    cx.update_editor(|editor, window, cx| {
19867        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19868    });
19869    executor.run_until_parked();
19870
19871    cx.assert_state_with_diff(
19872        r#"
19873        use some::mod1;
19874        use some::mod2;
19875
19876        const A: u32 = 42;
19877        const B: u32 = 42;
19878      - const C: u32 = 42;
19879      + const C: u32 = 43ˇ
19880        const D: u32 = 42;
19881
19882
19883        fn main() {
19884            println!("hello");
19885
19886            println!("world");
19887        }"#
19888        .unindent(),
19889    );
19890
19891    cx.update_editor(|editor, window, cx| {
19892        editor.handle_input("\nnew_line\n", window, cx);
19893    });
19894    executor.run_until_parked();
19895
19896    cx.assert_state_with_diff(
19897        r#"
19898        use some::mod1;
19899        use some::mod2;
19900
19901        const A: u32 = 42;
19902        const B: u32 = 42;
19903      - const C: u32 = 42;
19904      + const C: u32 = 43
19905      + new_line
19906      + ˇ
19907        const D: u32 = 42;
19908
19909
19910        fn main() {
19911            println!("hello");
19912
19913            println!("world");
19914        }"#
19915        .unindent(),
19916    );
19917}
19918
19919#[gpui::test]
19920async fn test_stage_and_unstage_added_file_hunk(
19921    executor: BackgroundExecutor,
19922    cx: &mut TestAppContext,
19923) {
19924    init_test(cx, |_| {});
19925
19926    let mut cx = EditorTestContext::new(cx).await;
19927    cx.update_editor(|editor, _, cx| {
19928        editor.set_expand_all_diff_hunks(cx);
19929    });
19930
19931    let working_copy = r#"
19932            ˇfn main() {
19933                println!("hello, world!");
19934            }
19935        "#
19936    .unindent();
19937
19938    cx.set_state(&working_copy);
19939    executor.run_until_parked();
19940
19941    cx.assert_state_with_diff(
19942        r#"
19943            + ˇfn main() {
19944            +     println!("hello, world!");
19945            + }
19946        "#
19947        .unindent(),
19948    );
19949    cx.assert_index_text(None);
19950
19951    cx.update_editor(|editor, window, cx| {
19952        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19953    });
19954    executor.run_until_parked();
19955    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
19956    cx.assert_state_with_diff(
19957        r#"
19958            + ˇfn main() {
19959            +     println!("hello, world!");
19960            + }
19961        "#
19962        .unindent(),
19963    );
19964
19965    cx.update_editor(|editor, window, cx| {
19966        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19967    });
19968    executor.run_until_parked();
19969    cx.assert_index_text(None);
19970}
19971
19972async fn setup_indent_guides_editor(
19973    text: &str,
19974    cx: &mut TestAppContext,
19975) -> (BufferId, EditorTestContext) {
19976    init_test(cx, |_| {});
19977
19978    let mut cx = EditorTestContext::new(cx).await;
19979
19980    let buffer_id = cx.update_editor(|editor, window, cx| {
19981        editor.set_text(text, window, cx);
19982        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
19983
19984        buffer_ids[0]
19985    });
19986
19987    (buffer_id, cx)
19988}
19989
19990fn assert_indent_guides(
19991    range: Range<u32>,
19992    expected: Vec<IndentGuide>,
19993    active_indices: Option<Vec<usize>>,
19994    cx: &mut EditorTestContext,
19995) {
19996    let indent_guides = cx.update_editor(|editor, window, cx| {
19997        let snapshot = editor.snapshot(window, cx).display_snapshot;
19998        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
19999            editor,
20000            MultiBufferRow(range.start)..MultiBufferRow(range.end),
20001            true,
20002            &snapshot,
20003            cx,
20004        );
20005
20006        indent_guides.sort_by(|a, b| {
20007            a.depth.cmp(&b.depth).then(
20008                a.start_row
20009                    .cmp(&b.start_row)
20010                    .then(a.end_row.cmp(&b.end_row)),
20011            )
20012        });
20013        indent_guides
20014    });
20015
20016    if let Some(expected) = active_indices {
20017        let active_indices = cx.update_editor(|editor, window, cx| {
20018            let snapshot = editor.snapshot(window, cx).display_snapshot;
20019            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
20020        });
20021
20022        assert_eq!(
20023            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
20024            expected,
20025            "Active indent guide indices do not match"
20026        );
20027    }
20028
20029    assert_eq!(indent_guides, expected, "Indent guides do not match");
20030}
20031
20032fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
20033    IndentGuide {
20034        buffer_id,
20035        start_row: MultiBufferRow(start_row),
20036        end_row: MultiBufferRow(end_row),
20037        depth,
20038        tab_size: 4,
20039        settings: IndentGuideSettings {
20040            enabled: true,
20041            line_width: 1,
20042            active_line_width: 1,
20043            ..Default::default()
20044        },
20045    }
20046}
20047
20048#[gpui::test]
20049async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
20050    let (buffer_id, mut cx) = setup_indent_guides_editor(
20051        &"
20052        fn main() {
20053            let a = 1;
20054        }"
20055        .unindent(),
20056        cx,
20057    )
20058    .await;
20059
20060    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20061}
20062
20063#[gpui::test]
20064async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
20065    let (buffer_id, mut cx) = setup_indent_guides_editor(
20066        &"
20067        fn main() {
20068            let a = 1;
20069            let b = 2;
20070        }"
20071        .unindent(),
20072        cx,
20073    )
20074    .await;
20075
20076    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
20077}
20078
20079#[gpui::test]
20080async fn test_indent_guide_nested(cx: &mut TestAppContext) {
20081    let (buffer_id, mut cx) = setup_indent_guides_editor(
20082        &"
20083        fn main() {
20084            let a = 1;
20085            if a == 3 {
20086                let b = 2;
20087            } else {
20088                let c = 3;
20089            }
20090        }"
20091        .unindent(),
20092        cx,
20093    )
20094    .await;
20095
20096    assert_indent_guides(
20097        0..8,
20098        vec![
20099            indent_guide(buffer_id, 1, 6, 0),
20100            indent_guide(buffer_id, 3, 3, 1),
20101            indent_guide(buffer_id, 5, 5, 1),
20102        ],
20103        None,
20104        &mut cx,
20105    );
20106}
20107
20108#[gpui::test]
20109async fn test_indent_guide_tab(cx: &mut TestAppContext) {
20110    let (buffer_id, mut cx) = setup_indent_guides_editor(
20111        &"
20112        fn main() {
20113            let a = 1;
20114                let b = 2;
20115            let c = 3;
20116        }"
20117        .unindent(),
20118        cx,
20119    )
20120    .await;
20121
20122    assert_indent_guides(
20123        0..5,
20124        vec![
20125            indent_guide(buffer_id, 1, 3, 0),
20126            indent_guide(buffer_id, 2, 2, 1),
20127        ],
20128        None,
20129        &mut cx,
20130    );
20131}
20132
20133#[gpui::test]
20134async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
20135    let (buffer_id, mut cx) = setup_indent_guides_editor(
20136        &"
20137        fn main() {
20138            let a = 1;
20139
20140            let c = 3;
20141        }"
20142        .unindent(),
20143        cx,
20144    )
20145    .await;
20146
20147    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
20148}
20149
20150#[gpui::test]
20151async fn test_indent_guide_complex(cx: &mut TestAppContext) {
20152    let (buffer_id, mut cx) = setup_indent_guides_editor(
20153        &"
20154        fn main() {
20155            let a = 1;
20156
20157            let c = 3;
20158
20159            if a == 3 {
20160                let b = 2;
20161            } else {
20162                let c = 3;
20163            }
20164        }"
20165        .unindent(),
20166        cx,
20167    )
20168    .await;
20169
20170    assert_indent_guides(
20171        0..11,
20172        vec![
20173            indent_guide(buffer_id, 1, 9, 0),
20174            indent_guide(buffer_id, 6, 6, 1),
20175            indent_guide(buffer_id, 8, 8, 1),
20176        ],
20177        None,
20178        &mut cx,
20179    );
20180}
20181
20182#[gpui::test]
20183async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
20184    let (buffer_id, mut cx) = setup_indent_guides_editor(
20185        &"
20186        fn main() {
20187            let a = 1;
20188
20189            let c = 3;
20190
20191            if a == 3 {
20192                let b = 2;
20193            } else {
20194                let c = 3;
20195            }
20196        }"
20197        .unindent(),
20198        cx,
20199    )
20200    .await;
20201
20202    assert_indent_guides(
20203        1..11,
20204        vec![
20205            indent_guide(buffer_id, 1, 9, 0),
20206            indent_guide(buffer_id, 6, 6, 1),
20207            indent_guide(buffer_id, 8, 8, 1),
20208        ],
20209        None,
20210        &mut cx,
20211    );
20212}
20213
20214#[gpui::test]
20215async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
20216    let (buffer_id, mut cx) = setup_indent_guides_editor(
20217        &"
20218        fn main() {
20219            let a = 1;
20220
20221            let c = 3;
20222
20223            if a == 3 {
20224                let b = 2;
20225            } else {
20226                let c = 3;
20227            }
20228        }"
20229        .unindent(),
20230        cx,
20231    )
20232    .await;
20233
20234    assert_indent_guides(
20235        1..10,
20236        vec![
20237            indent_guide(buffer_id, 1, 9, 0),
20238            indent_guide(buffer_id, 6, 6, 1),
20239            indent_guide(buffer_id, 8, 8, 1),
20240        ],
20241        None,
20242        &mut cx,
20243    );
20244}
20245
20246#[gpui::test]
20247async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
20248    let (buffer_id, mut cx) = setup_indent_guides_editor(
20249        &"
20250        fn main() {
20251            if a {
20252                b(
20253                    c,
20254                    d,
20255                )
20256            } else {
20257                e(
20258                    f
20259                )
20260            }
20261        }"
20262        .unindent(),
20263        cx,
20264    )
20265    .await;
20266
20267    assert_indent_guides(
20268        0..11,
20269        vec![
20270            indent_guide(buffer_id, 1, 10, 0),
20271            indent_guide(buffer_id, 2, 5, 1),
20272            indent_guide(buffer_id, 7, 9, 1),
20273            indent_guide(buffer_id, 3, 4, 2),
20274            indent_guide(buffer_id, 8, 8, 2),
20275        ],
20276        None,
20277        &mut cx,
20278    );
20279
20280    cx.update_editor(|editor, window, cx| {
20281        editor.fold_at(MultiBufferRow(2), window, cx);
20282        assert_eq!(
20283            editor.display_text(cx),
20284            "
20285            fn main() {
20286                if a {
20287                    b(⋯
20288                    )
20289                } else {
20290                    e(
20291                        f
20292                    )
20293                }
20294            }"
20295            .unindent()
20296        );
20297    });
20298
20299    assert_indent_guides(
20300        0..11,
20301        vec![
20302            indent_guide(buffer_id, 1, 10, 0),
20303            indent_guide(buffer_id, 2, 5, 1),
20304            indent_guide(buffer_id, 7, 9, 1),
20305            indent_guide(buffer_id, 8, 8, 2),
20306        ],
20307        None,
20308        &mut cx,
20309    );
20310}
20311
20312#[gpui::test]
20313async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
20314    let (buffer_id, mut cx) = setup_indent_guides_editor(
20315        &"
20316        block1
20317            block2
20318                block3
20319                    block4
20320            block2
20321        block1
20322        block1"
20323            .unindent(),
20324        cx,
20325    )
20326    .await;
20327
20328    assert_indent_guides(
20329        1..10,
20330        vec![
20331            indent_guide(buffer_id, 1, 4, 0),
20332            indent_guide(buffer_id, 2, 3, 1),
20333            indent_guide(buffer_id, 3, 3, 2),
20334        ],
20335        None,
20336        &mut cx,
20337    );
20338}
20339
20340#[gpui::test]
20341async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
20342    let (buffer_id, mut cx) = setup_indent_guides_editor(
20343        &"
20344        block1
20345            block2
20346                block3
20347
20348        block1
20349        block1"
20350            .unindent(),
20351        cx,
20352    )
20353    .await;
20354
20355    assert_indent_guides(
20356        0..6,
20357        vec![
20358            indent_guide(buffer_id, 1, 2, 0),
20359            indent_guide(buffer_id, 2, 2, 1),
20360        ],
20361        None,
20362        &mut cx,
20363    );
20364}
20365
20366#[gpui::test]
20367async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
20368    let (buffer_id, mut cx) = setup_indent_guides_editor(
20369        &"
20370        function component() {
20371        \treturn (
20372        \t\t\t
20373        \t\t<div>
20374        \t\t\t<abc></abc>
20375        \t\t</div>
20376        \t)
20377        }"
20378        .unindent(),
20379        cx,
20380    )
20381    .await;
20382
20383    assert_indent_guides(
20384        0..8,
20385        vec![
20386            indent_guide(buffer_id, 1, 6, 0),
20387            indent_guide(buffer_id, 2, 5, 1),
20388            indent_guide(buffer_id, 4, 4, 2),
20389        ],
20390        None,
20391        &mut cx,
20392    );
20393}
20394
20395#[gpui::test]
20396async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
20397    let (buffer_id, mut cx) = setup_indent_guides_editor(
20398        &"
20399        function component() {
20400        \treturn (
20401        \t
20402        \t\t<div>
20403        \t\t\t<abc></abc>
20404        \t\t</div>
20405        \t)
20406        }"
20407        .unindent(),
20408        cx,
20409    )
20410    .await;
20411
20412    assert_indent_guides(
20413        0..8,
20414        vec![
20415            indent_guide(buffer_id, 1, 6, 0),
20416            indent_guide(buffer_id, 2, 5, 1),
20417            indent_guide(buffer_id, 4, 4, 2),
20418        ],
20419        None,
20420        &mut cx,
20421    );
20422}
20423
20424#[gpui::test]
20425async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
20426    let (buffer_id, mut cx) = setup_indent_guides_editor(
20427        &"
20428        block1
20429
20430
20431
20432            block2
20433        "
20434        .unindent(),
20435        cx,
20436    )
20437    .await;
20438
20439    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20440}
20441
20442#[gpui::test]
20443async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
20444    let (buffer_id, mut cx) = setup_indent_guides_editor(
20445        &"
20446        def a:
20447        \tb = 3
20448        \tif True:
20449        \t\tc = 4
20450        \t\td = 5
20451        \tprint(b)
20452        "
20453        .unindent(),
20454        cx,
20455    )
20456    .await;
20457
20458    assert_indent_guides(
20459        0..6,
20460        vec![
20461            indent_guide(buffer_id, 1, 5, 0),
20462            indent_guide(buffer_id, 3, 4, 1),
20463        ],
20464        None,
20465        &mut cx,
20466    );
20467}
20468
20469#[gpui::test]
20470async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
20471    let (buffer_id, mut cx) = setup_indent_guides_editor(
20472        &"
20473    fn main() {
20474        let a = 1;
20475    }"
20476        .unindent(),
20477        cx,
20478    )
20479    .await;
20480
20481    cx.update_editor(|editor, window, cx| {
20482        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20483            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20484        });
20485    });
20486
20487    assert_indent_guides(
20488        0..3,
20489        vec![indent_guide(buffer_id, 1, 1, 0)],
20490        Some(vec![0]),
20491        &mut cx,
20492    );
20493}
20494
20495#[gpui::test]
20496async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
20497    let (buffer_id, mut cx) = setup_indent_guides_editor(
20498        &"
20499    fn main() {
20500        if 1 == 2 {
20501            let a = 1;
20502        }
20503    }"
20504        .unindent(),
20505        cx,
20506    )
20507    .await;
20508
20509    cx.update_editor(|editor, window, cx| {
20510        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20511            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20512        });
20513    });
20514
20515    assert_indent_guides(
20516        0..4,
20517        vec![
20518            indent_guide(buffer_id, 1, 3, 0),
20519            indent_guide(buffer_id, 2, 2, 1),
20520        ],
20521        Some(vec![1]),
20522        &mut cx,
20523    );
20524
20525    cx.update_editor(|editor, window, cx| {
20526        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20527            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20528        });
20529    });
20530
20531    assert_indent_guides(
20532        0..4,
20533        vec![
20534            indent_guide(buffer_id, 1, 3, 0),
20535            indent_guide(buffer_id, 2, 2, 1),
20536        ],
20537        Some(vec![1]),
20538        &mut cx,
20539    );
20540
20541    cx.update_editor(|editor, window, cx| {
20542        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20543            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
20544        });
20545    });
20546
20547    assert_indent_guides(
20548        0..4,
20549        vec![
20550            indent_guide(buffer_id, 1, 3, 0),
20551            indent_guide(buffer_id, 2, 2, 1),
20552        ],
20553        Some(vec![0]),
20554        &mut cx,
20555    );
20556}
20557
20558#[gpui::test]
20559async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
20560    let (buffer_id, mut cx) = setup_indent_guides_editor(
20561        &"
20562    fn main() {
20563        let a = 1;
20564
20565        let b = 2;
20566    }"
20567        .unindent(),
20568        cx,
20569    )
20570    .await;
20571
20572    cx.update_editor(|editor, window, cx| {
20573        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20574            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20575        });
20576    });
20577
20578    assert_indent_guides(
20579        0..5,
20580        vec![indent_guide(buffer_id, 1, 3, 0)],
20581        Some(vec![0]),
20582        &mut cx,
20583    );
20584}
20585
20586#[gpui::test]
20587async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
20588    let (buffer_id, mut cx) = setup_indent_guides_editor(
20589        &"
20590    def m:
20591        a = 1
20592        pass"
20593            .unindent(),
20594        cx,
20595    )
20596    .await;
20597
20598    cx.update_editor(|editor, window, cx| {
20599        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20600            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20601        });
20602    });
20603
20604    assert_indent_guides(
20605        0..3,
20606        vec![indent_guide(buffer_id, 1, 2, 0)],
20607        Some(vec![0]),
20608        &mut cx,
20609    );
20610}
20611
20612#[gpui::test]
20613async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
20614    init_test(cx, |_| {});
20615    let mut cx = EditorTestContext::new(cx).await;
20616    let text = indoc! {
20617        "
20618        impl A {
20619            fn b() {
20620                0;
20621                3;
20622                5;
20623                6;
20624                7;
20625            }
20626        }
20627        "
20628    };
20629    let base_text = indoc! {
20630        "
20631        impl A {
20632            fn b() {
20633                0;
20634                1;
20635                2;
20636                3;
20637                4;
20638            }
20639            fn c() {
20640                5;
20641                6;
20642                7;
20643            }
20644        }
20645        "
20646    };
20647
20648    cx.update_editor(|editor, window, cx| {
20649        editor.set_text(text, window, cx);
20650
20651        editor.buffer().update(cx, |multibuffer, cx| {
20652            let buffer = multibuffer.as_singleton().unwrap();
20653            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
20654
20655            multibuffer.set_all_diff_hunks_expanded(cx);
20656            multibuffer.add_diff(diff, cx);
20657
20658            buffer.read(cx).remote_id()
20659        })
20660    });
20661    cx.run_until_parked();
20662
20663    cx.assert_state_with_diff(
20664        indoc! { "
20665          impl A {
20666              fn b() {
20667                  0;
20668        -         1;
20669        -         2;
20670                  3;
20671        -         4;
20672        -     }
20673        -     fn c() {
20674                  5;
20675                  6;
20676                  7;
20677              }
20678          }
20679          ˇ"
20680        }
20681        .to_string(),
20682    );
20683
20684    let mut actual_guides = cx.update_editor(|editor, window, cx| {
20685        editor
20686            .snapshot(window, cx)
20687            .buffer_snapshot
20688            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
20689            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
20690            .collect::<Vec<_>>()
20691    });
20692    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
20693    assert_eq!(
20694        actual_guides,
20695        vec![
20696            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
20697            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
20698            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
20699        ]
20700    );
20701}
20702
20703#[gpui::test]
20704async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
20705    init_test(cx, |_| {});
20706    let mut cx = EditorTestContext::new(cx).await;
20707
20708    let diff_base = r#"
20709        a
20710        b
20711        c
20712        "#
20713    .unindent();
20714
20715    cx.set_state(
20716        &r#"
20717        ˇA
20718        b
20719        C
20720        "#
20721        .unindent(),
20722    );
20723    cx.set_head_text(&diff_base);
20724    cx.update_editor(|editor, window, cx| {
20725        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20726    });
20727    executor.run_until_parked();
20728
20729    let both_hunks_expanded = r#"
20730        - a
20731        + ˇA
20732          b
20733        - c
20734        + C
20735        "#
20736    .unindent();
20737
20738    cx.assert_state_with_diff(both_hunks_expanded.clone());
20739
20740    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20741        let snapshot = editor.snapshot(window, cx);
20742        let hunks = editor
20743            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20744            .collect::<Vec<_>>();
20745        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20746        let buffer_id = hunks[0].buffer_id;
20747        hunks
20748            .into_iter()
20749            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20750            .collect::<Vec<_>>()
20751    });
20752    assert_eq!(hunk_ranges.len(), 2);
20753
20754    cx.update_editor(|editor, _, cx| {
20755        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20756    });
20757    executor.run_until_parked();
20758
20759    let second_hunk_expanded = r#"
20760          ˇA
20761          b
20762        - c
20763        + C
20764        "#
20765    .unindent();
20766
20767    cx.assert_state_with_diff(second_hunk_expanded);
20768
20769    cx.update_editor(|editor, _, cx| {
20770        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20771    });
20772    executor.run_until_parked();
20773
20774    cx.assert_state_with_diff(both_hunks_expanded.clone());
20775
20776    cx.update_editor(|editor, _, cx| {
20777        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20778    });
20779    executor.run_until_parked();
20780
20781    let first_hunk_expanded = r#"
20782        - a
20783        + ˇA
20784          b
20785          C
20786        "#
20787    .unindent();
20788
20789    cx.assert_state_with_diff(first_hunk_expanded);
20790
20791    cx.update_editor(|editor, _, cx| {
20792        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20793    });
20794    executor.run_until_parked();
20795
20796    cx.assert_state_with_diff(both_hunks_expanded);
20797
20798    cx.set_state(
20799        &r#"
20800        ˇA
20801        b
20802        "#
20803        .unindent(),
20804    );
20805    cx.run_until_parked();
20806
20807    // TODO this cursor position seems bad
20808    cx.assert_state_with_diff(
20809        r#"
20810        - ˇa
20811        + A
20812          b
20813        "#
20814        .unindent(),
20815    );
20816
20817    cx.update_editor(|editor, window, cx| {
20818        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20819    });
20820
20821    cx.assert_state_with_diff(
20822        r#"
20823            - ˇa
20824            + A
20825              b
20826            - c
20827            "#
20828        .unindent(),
20829    );
20830
20831    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20832        let snapshot = editor.snapshot(window, cx);
20833        let hunks = editor
20834            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20835            .collect::<Vec<_>>();
20836        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20837        let buffer_id = hunks[0].buffer_id;
20838        hunks
20839            .into_iter()
20840            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20841            .collect::<Vec<_>>()
20842    });
20843    assert_eq!(hunk_ranges.len(), 2);
20844
20845    cx.update_editor(|editor, _, cx| {
20846        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20847    });
20848    executor.run_until_parked();
20849
20850    cx.assert_state_with_diff(
20851        r#"
20852        - ˇa
20853        + A
20854          b
20855        "#
20856        .unindent(),
20857    );
20858}
20859
20860#[gpui::test]
20861async fn test_toggle_deletion_hunk_at_start_of_file(
20862    executor: BackgroundExecutor,
20863    cx: &mut TestAppContext,
20864) {
20865    init_test(cx, |_| {});
20866    let mut cx = EditorTestContext::new(cx).await;
20867
20868    let diff_base = r#"
20869        a
20870        b
20871        c
20872        "#
20873    .unindent();
20874
20875    cx.set_state(
20876        &r#"
20877        ˇb
20878        c
20879        "#
20880        .unindent(),
20881    );
20882    cx.set_head_text(&diff_base);
20883    cx.update_editor(|editor, window, cx| {
20884        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20885    });
20886    executor.run_until_parked();
20887
20888    let hunk_expanded = r#"
20889        - a
20890          ˇb
20891          c
20892        "#
20893    .unindent();
20894
20895    cx.assert_state_with_diff(hunk_expanded.clone());
20896
20897    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20898        let snapshot = editor.snapshot(window, cx);
20899        let hunks = editor
20900            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20901            .collect::<Vec<_>>();
20902        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20903        let buffer_id = hunks[0].buffer_id;
20904        hunks
20905            .into_iter()
20906            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20907            .collect::<Vec<_>>()
20908    });
20909    assert_eq!(hunk_ranges.len(), 1);
20910
20911    cx.update_editor(|editor, _, cx| {
20912        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20913    });
20914    executor.run_until_parked();
20915
20916    let hunk_collapsed = r#"
20917          ˇb
20918          c
20919        "#
20920    .unindent();
20921
20922    cx.assert_state_with_diff(hunk_collapsed);
20923
20924    cx.update_editor(|editor, _, cx| {
20925        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20926    });
20927    executor.run_until_parked();
20928
20929    cx.assert_state_with_diff(hunk_expanded);
20930}
20931
20932#[gpui::test]
20933async fn test_display_diff_hunks(cx: &mut TestAppContext) {
20934    init_test(cx, |_| {});
20935
20936    let fs = FakeFs::new(cx.executor());
20937    fs.insert_tree(
20938        path!("/test"),
20939        json!({
20940            ".git": {},
20941            "file-1": "ONE\n",
20942            "file-2": "TWO\n",
20943            "file-3": "THREE\n",
20944        }),
20945    )
20946    .await;
20947
20948    fs.set_head_for_repo(
20949        path!("/test/.git").as_ref(),
20950        &[
20951            ("file-1".into(), "one\n".into()),
20952            ("file-2".into(), "two\n".into()),
20953            ("file-3".into(), "three\n".into()),
20954        ],
20955        "deadbeef",
20956    );
20957
20958    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
20959    let mut buffers = vec![];
20960    for i in 1..=3 {
20961        let buffer = project
20962            .update(cx, |project, cx| {
20963                let path = format!(path!("/test/file-{}"), i);
20964                project.open_local_buffer(path, cx)
20965            })
20966            .await
20967            .unwrap();
20968        buffers.push(buffer);
20969    }
20970
20971    let multibuffer = cx.new(|cx| {
20972        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
20973        multibuffer.set_all_diff_hunks_expanded(cx);
20974        for buffer in &buffers {
20975            let snapshot = buffer.read(cx).snapshot();
20976            multibuffer.set_excerpts_for_path(
20977                PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
20978                buffer.clone(),
20979                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
20980                2,
20981                cx,
20982            );
20983        }
20984        multibuffer
20985    });
20986
20987    let editor = cx.add_window(|window, cx| {
20988        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
20989    });
20990    cx.run_until_parked();
20991
20992    let snapshot = editor
20993        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
20994        .unwrap();
20995    let hunks = snapshot
20996        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
20997        .map(|hunk| match hunk {
20998            DisplayDiffHunk::Unfolded {
20999                display_row_range, ..
21000            } => display_row_range,
21001            DisplayDiffHunk::Folded { .. } => unreachable!(),
21002        })
21003        .collect::<Vec<_>>();
21004    assert_eq!(
21005        hunks,
21006        [
21007            DisplayRow(2)..DisplayRow(4),
21008            DisplayRow(7)..DisplayRow(9),
21009            DisplayRow(12)..DisplayRow(14),
21010        ]
21011    );
21012}
21013
21014#[gpui::test]
21015async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
21016    init_test(cx, |_| {});
21017
21018    let mut cx = EditorTestContext::new(cx).await;
21019    cx.set_head_text(indoc! { "
21020        one
21021        two
21022        three
21023        four
21024        five
21025        "
21026    });
21027    cx.set_index_text(indoc! { "
21028        one
21029        two
21030        three
21031        four
21032        five
21033        "
21034    });
21035    cx.set_state(indoc! {"
21036        one
21037        TWO
21038        ˇTHREE
21039        FOUR
21040        five
21041    "});
21042    cx.run_until_parked();
21043    cx.update_editor(|editor, window, cx| {
21044        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21045    });
21046    cx.run_until_parked();
21047    cx.assert_index_text(Some(indoc! {"
21048        one
21049        TWO
21050        THREE
21051        FOUR
21052        five
21053    "}));
21054    cx.set_state(indoc! { "
21055        one
21056        TWO
21057        ˇTHREE-HUNDRED
21058        FOUR
21059        five
21060    "});
21061    cx.run_until_parked();
21062    cx.update_editor(|editor, window, cx| {
21063        let snapshot = editor.snapshot(window, cx);
21064        let hunks = editor
21065            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
21066            .collect::<Vec<_>>();
21067        assert_eq!(hunks.len(), 1);
21068        assert_eq!(
21069            hunks[0].status(),
21070            DiffHunkStatus {
21071                kind: DiffHunkStatusKind::Modified,
21072                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
21073            }
21074        );
21075
21076        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21077    });
21078    cx.run_until_parked();
21079    cx.assert_index_text(Some(indoc! {"
21080        one
21081        TWO
21082        THREE-HUNDRED
21083        FOUR
21084        five
21085    "}));
21086}
21087
21088#[gpui::test]
21089fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
21090    init_test(cx, |_| {});
21091
21092    let editor = cx.add_window(|window, cx| {
21093        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
21094        build_editor(buffer, window, cx)
21095    });
21096
21097    let render_args = Arc::new(Mutex::new(None));
21098    let snapshot = editor
21099        .update(cx, |editor, window, cx| {
21100            let snapshot = editor.buffer().read(cx).snapshot(cx);
21101            let range =
21102                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
21103
21104            struct RenderArgs {
21105                row: MultiBufferRow,
21106                folded: bool,
21107                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
21108            }
21109
21110            let crease = Crease::inline(
21111                range,
21112                FoldPlaceholder::test(),
21113                {
21114                    let toggle_callback = render_args.clone();
21115                    move |row, folded, callback, _window, _cx| {
21116                        *toggle_callback.lock() = Some(RenderArgs {
21117                            row,
21118                            folded,
21119                            callback,
21120                        });
21121                        div()
21122                    }
21123                },
21124                |_row, _folded, _window, _cx| div(),
21125            );
21126
21127            editor.insert_creases(Some(crease), cx);
21128            let snapshot = editor.snapshot(window, cx);
21129            let _div =
21130                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
21131            snapshot
21132        })
21133        .unwrap();
21134
21135    let render_args = render_args.lock().take().unwrap();
21136    assert_eq!(render_args.row, MultiBufferRow(1));
21137    assert!(!render_args.folded);
21138    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21139
21140    cx.update_window(*editor, |_, window, cx| {
21141        (render_args.callback)(true, window, cx)
21142    })
21143    .unwrap();
21144    let snapshot = editor
21145        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21146        .unwrap();
21147    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
21148
21149    cx.update_window(*editor, |_, window, cx| {
21150        (render_args.callback)(false, window, cx)
21151    })
21152    .unwrap();
21153    let snapshot = editor
21154        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21155        .unwrap();
21156    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21157}
21158
21159#[gpui::test]
21160async fn test_input_text(cx: &mut TestAppContext) {
21161    init_test(cx, |_| {});
21162    let mut cx = EditorTestContext::new(cx).await;
21163
21164    cx.set_state(
21165        &r#"ˇone
21166        two
21167
21168        three
21169        fourˇ
21170        five
21171
21172        siˇx"#
21173            .unindent(),
21174    );
21175
21176    cx.dispatch_action(HandleInput(String::new()));
21177    cx.assert_editor_state(
21178        &r#"ˇone
21179        two
21180
21181        three
21182        fourˇ
21183        five
21184
21185        siˇx"#
21186            .unindent(),
21187    );
21188
21189    cx.dispatch_action(HandleInput("AAAA".to_string()));
21190    cx.assert_editor_state(
21191        &r#"AAAAˇone
21192        two
21193
21194        three
21195        fourAAAAˇ
21196        five
21197
21198        siAAAAˇx"#
21199            .unindent(),
21200    );
21201}
21202
21203#[gpui::test]
21204async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
21205    init_test(cx, |_| {});
21206
21207    let mut cx = EditorTestContext::new(cx).await;
21208    cx.set_state(
21209        r#"let foo = 1;
21210let foo = 2;
21211let foo = 3;
21212let fooˇ = 4;
21213let foo = 5;
21214let foo = 6;
21215let foo = 7;
21216let foo = 8;
21217let foo = 9;
21218let foo = 10;
21219let foo = 11;
21220let foo = 12;
21221let foo = 13;
21222let foo = 14;
21223let foo = 15;"#,
21224    );
21225
21226    cx.update_editor(|e, window, cx| {
21227        assert_eq!(
21228            e.next_scroll_position,
21229            NextScrollCursorCenterTopBottom::Center,
21230            "Default next scroll direction is center",
21231        );
21232
21233        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21234        assert_eq!(
21235            e.next_scroll_position,
21236            NextScrollCursorCenterTopBottom::Top,
21237            "After center, next scroll direction should be top",
21238        );
21239
21240        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21241        assert_eq!(
21242            e.next_scroll_position,
21243            NextScrollCursorCenterTopBottom::Bottom,
21244            "After top, next scroll direction should be bottom",
21245        );
21246
21247        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21248        assert_eq!(
21249            e.next_scroll_position,
21250            NextScrollCursorCenterTopBottom::Center,
21251            "After bottom, scrolling should start over",
21252        );
21253
21254        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21255        assert_eq!(
21256            e.next_scroll_position,
21257            NextScrollCursorCenterTopBottom::Top,
21258            "Scrolling continues if retriggered fast enough"
21259        );
21260    });
21261
21262    cx.executor()
21263        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
21264    cx.executor().run_until_parked();
21265    cx.update_editor(|e, _, _| {
21266        assert_eq!(
21267            e.next_scroll_position,
21268            NextScrollCursorCenterTopBottom::Center,
21269            "If scrolling is not triggered fast enough, it should reset"
21270        );
21271    });
21272}
21273
21274#[gpui::test]
21275async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
21276    init_test(cx, |_| {});
21277    let mut cx = EditorLspTestContext::new_rust(
21278        lsp::ServerCapabilities {
21279            definition_provider: Some(lsp::OneOf::Left(true)),
21280            references_provider: Some(lsp::OneOf::Left(true)),
21281            ..lsp::ServerCapabilities::default()
21282        },
21283        cx,
21284    )
21285    .await;
21286
21287    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
21288        let go_to_definition = cx
21289            .lsp
21290            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21291                move |params, _| async move {
21292                    if empty_go_to_definition {
21293                        Ok(None)
21294                    } else {
21295                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
21296                            uri: params.text_document_position_params.text_document.uri,
21297                            range: lsp::Range::new(
21298                                lsp::Position::new(4, 3),
21299                                lsp::Position::new(4, 6),
21300                            ),
21301                        })))
21302                    }
21303                },
21304            );
21305        let references = cx
21306            .lsp
21307            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21308                Ok(Some(vec![lsp::Location {
21309                    uri: params.text_document_position.text_document.uri,
21310                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
21311                }]))
21312            });
21313        (go_to_definition, references)
21314    };
21315
21316    cx.set_state(
21317        &r#"fn one() {
21318            let mut a = ˇtwo();
21319        }
21320
21321        fn two() {}"#
21322            .unindent(),
21323    );
21324    set_up_lsp_handlers(false, &mut cx);
21325    let navigated = cx
21326        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21327        .await
21328        .expect("Failed to navigate to definition");
21329    assert_eq!(
21330        navigated,
21331        Navigated::Yes,
21332        "Should have navigated to definition from the GetDefinition response"
21333    );
21334    cx.assert_editor_state(
21335        &r#"fn one() {
21336            let mut a = two();
21337        }
21338
21339        fn «twoˇ»() {}"#
21340            .unindent(),
21341    );
21342
21343    let editors = cx.update_workspace(|workspace, _, cx| {
21344        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21345    });
21346    cx.update_editor(|_, _, test_editor_cx| {
21347        assert_eq!(
21348            editors.len(),
21349            1,
21350            "Initially, only one, test, editor should be open in the workspace"
21351        );
21352        assert_eq!(
21353            test_editor_cx.entity(),
21354            editors.last().expect("Asserted len is 1").clone()
21355        );
21356    });
21357
21358    set_up_lsp_handlers(true, &mut cx);
21359    let navigated = cx
21360        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21361        .await
21362        .expect("Failed to navigate to lookup references");
21363    assert_eq!(
21364        navigated,
21365        Navigated::Yes,
21366        "Should have navigated to references as a fallback after empty GoToDefinition response"
21367    );
21368    // We should not change the selections in the existing file,
21369    // if opening another milti buffer with the references
21370    cx.assert_editor_state(
21371        &r#"fn one() {
21372            let mut a = two();
21373        }
21374
21375        fn «twoˇ»() {}"#
21376            .unindent(),
21377    );
21378    let editors = cx.update_workspace(|workspace, _, cx| {
21379        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21380    });
21381    cx.update_editor(|_, _, test_editor_cx| {
21382        assert_eq!(
21383            editors.len(),
21384            2,
21385            "After falling back to references search, we open a new editor with the results"
21386        );
21387        let references_fallback_text = editors
21388            .into_iter()
21389            .find(|new_editor| *new_editor != test_editor_cx.entity())
21390            .expect("Should have one non-test editor now")
21391            .read(test_editor_cx)
21392            .text(test_editor_cx);
21393        assert_eq!(
21394            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
21395            "Should use the range from the references response and not the GoToDefinition one"
21396        );
21397    });
21398}
21399
21400#[gpui::test]
21401async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
21402    init_test(cx, |_| {});
21403    cx.update(|cx| {
21404        let mut editor_settings = EditorSettings::get_global(cx).clone();
21405        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
21406        EditorSettings::override_global(editor_settings, cx);
21407    });
21408    let mut cx = EditorLspTestContext::new_rust(
21409        lsp::ServerCapabilities {
21410            definition_provider: Some(lsp::OneOf::Left(true)),
21411            references_provider: Some(lsp::OneOf::Left(true)),
21412            ..lsp::ServerCapabilities::default()
21413        },
21414        cx,
21415    )
21416    .await;
21417    let original_state = r#"fn one() {
21418        let mut a = ˇtwo();
21419    }
21420
21421    fn two() {}"#
21422        .unindent();
21423    cx.set_state(&original_state);
21424
21425    let mut go_to_definition = cx
21426        .lsp
21427        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21428            move |_, _| async move { Ok(None) },
21429        );
21430    let _references = cx
21431        .lsp
21432        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
21433            panic!("Should not call for references with no go to definition fallback")
21434        });
21435
21436    let navigated = cx
21437        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21438        .await
21439        .expect("Failed to navigate to lookup references");
21440    go_to_definition
21441        .next()
21442        .await
21443        .expect("Should have called the go_to_definition handler");
21444
21445    assert_eq!(
21446        navigated,
21447        Navigated::No,
21448        "Should have navigated to references as a fallback after empty GoToDefinition response"
21449    );
21450    cx.assert_editor_state(&original_state);
21451    let editors = cx.update_workspace(|workspace, _, cx| {
21452        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21453    });
21454    cx.update_editor(|_, _, _| {
21455        assert_eq!(
21456            editors.len(),
21457            1,
21458            "After unsuccessful fallback, no other editor should have been opened"
21459        );
21460    });
21461}
21462
21463#[gpui::test]
21464async fn test_find_all_references_editor_reuse(cx: &mut TestAppContext) {
21465    init_test(cx, |_| {});
21466    let mut cx = EditorLspTestContext::new_rust(
21467        lsp::ServerCapabilities {
21468            references_provider: Some(lsp::OneOf::Left(true)),
21469            ..lsp::ServerCapabilities::default()
21470        },
21471        cx,
21472    )
21473    .await;
21474
21475    cx.set_state(
21476        &r#"
21477        fn one() {
21478            let mut a = two();
21479        }
21480
21481        fn ˇtwo() {}"#
21482            .unindent(),
21483    );
21484    cx.lsp
21485        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21486            Ok(Some(vec![
21487                lsp::Location {
21488                    uri: params.text_document_position.text_document.uri.clone(),
21489                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
21490                },
21491                lsp::Location {
21492                    uri: params.text_document_position.text_document.uri,
21493                    range: lsp::Range::new(lsp::Position::new(4, 4), lsp::Position::new(4, 7)),
21494                },
21495            ]))
21496        });
21497    let navigated = cx
21498        .update_editor(|editor, window, cx| {
21499            editor.find_all_references(&FindAllReferences, window, cx)
21500        })
21501        .unwrap()
21502        .await
21503        .expect("Failed to navigate to references");
21504    assert_eq!(
21505        navigated,
21506        Navigated::Yes,
21507        "Should have navigated to references from the FindAllReferences response"
21508    );
21509    cx.assert_editor_state(
21510        &r#"fn one() {
21511            let mut a = two();
21512        }
21513
21514        fn ˇtwo() {}"#
21515            .unindent(),
21516    );
21517
21518    let editors = cx.update_workspace(|workspace, _, cx| {
21519        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21520    });
21521    cx.update_editor(|_, _, _| {
21522        assert_eq!(editors.len(), 2, "We should have opened a new multibuffer");
21523    });
21524
21525    cx.set_state(
21526        &r#"fn one() {
21527            let mut a = ˇtwo();
21528        }
21529
21530        fn two() {}"#
21531            .unindent(),
21532    );
21533    let navigated = cx
21534        .update_editor(|editor, window, cx| {
21535            editor.find_all_references(&FindAllReferences, window, cx)
21536        })
21537        .unwrap()
21538        .await
21539        .expect("Failed to navigate to references");
21540    assert_eq!(
21541        navigated,
21542        Navigated::Yes,
21543        "Should have navigated to references from the FindAllReferences response"
21544    );
21545    cx.assert_editor_state(
21546        &r#"fn one() {
21547            let mut a = ˇtwo();
21548        }
21549
21550        fn two() {}"#
21551            .unindent(),
21552    );
21553    let editors = cx.update_workspace(|workspace, _, cx| {
21554        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21555    });
21556    cx.update_editor(|_, _, _| {
21557        assert_eq!(
21558            editors.len(),
21559            2,
21560            "should have re-used the previous multibuffer"
21561        );
21562    });
21563
21564    cx.set_state(
21565        &r#"fn one() {
21566            let mut a = ˇtwo();
21567        }
21568        fn three() {}
21569        fn two() {}"#
21570            .unindent(),
21571    );
21572    cx.lsp
21573        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21574            Ok(Some(vec![
21575                lsp::Location {
21576                    uri: params.text_document_position.text_document.uri.clone(),
21577                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
21578                },
21579                lsp::Location {
21580                    uri: params.text_document_position.text_document.uri,
21581                    range: lsp::Range::new(lsp::Position::new(5, 4), lsp::Position::new(5, 7)),
21582                },
21583            ]))
21584        });
21585    let navigated = cx
21586        .update_editor(|editor, window, cx| {
21587            editor.find_all_references(&FindAllReferences, window, cx)
21588        })
21589        .unwrap()
21590        .await
21591        .expect("Failed to navigate to references");
21592    assert_eq!(
21593        navigated,
21594        Navigated::Yes,
21595        "Should have navigated to references from the FindAllReferences response"
21596    );
21597    cx.assert_editor_state(
21598        &r#"fn one() {
21599                let mut a = ˇtwo();
21600            }
21601            fn three() {}
21602            fn two() {}"#
21603            .unindent(),
21604    );
21605    let editors = cx.update_workspace(|workspace, _, cx| {
21606        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21607    });
21608    cx.update_editor(|_, _, _| {
21609        assert_eq!(
21610            editors.len(),
21611            3,
21612            "should have used a new multibuffer as offsets changed"
21613        );
21614    });
21615}
21616#[gpui::test]
21617async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
21618    init_test(cx, |_| {});
21619
21620    let language = Arc::new(Language::new(
21621        LanguageConfig::default(),
21622        Some(tree_sitter_rust::LANGUAGE.into()),
21623    ));
21624
21625    let text = r#"
21626        #[cfg(test)]
21627        mod tests() {
21628            #[test]
21629            fn runnable_1() {
21630                let a = 1;
21631            }
21632
21633            #[test]
21634            fn runnable_2() {
21635                let a = 1;
21636                let b = 2;
21637            }
21638        }
21639    "#
21640    .unindent();
21641
21642    let fs = FakeFs::new(cx.executor());
21643    fs.insert_file("/file.rs", Default::default()).await;
21644
21645    let project = Project::test(fs, ["/a".as_ref()], cx).await;
21646    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21647    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21648    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
21649    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
21650
21651    let editor = cx.new_window_entity(|window, cx| {
21652        Editor::new(
21653            EditorMode::full(),
21654            multi_buffer,
21655            Some(project.clone()),
21656            window,
21657            cx,
21658        )
21659    });
21660
21661    editor.update_in(cx, |editor, window, cx| {
21662        let snapshot = editor.buffer().read(cx).snapshot(cx);
21663        editor.tasks.insert(
21664            (buffer.read(cx).remote_id(), 3),
21665            RunnableTasks {
21666                templates: vec![],
21667                offset: snapshot.anchor_before(43),
21668                column: 0,
21669                extra_variables: HashMap::default(),
21670                context_range: BufferOffset(43)..BufferOffset(85),
21671            },
21672        );
21673        editor.tasks.insert(
21674            (buffer.read(cx).remote_id(), 8),
21675            RunnableTasks {
21676                templates: vec![],
21677                offset: snapshot.anchor_before(86),
21678                column: 0,
21679                extra_variables: HashMap::default(),
21680                context_range: BufferOffset(86)..BufferOffset(191),
21681            },
21682        );
21683
21684        // Test finding task when cursor is inside function body
21685        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21686            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
21687        });
21688        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
21689        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
21690
21691        // Test finding task when cursor is on function name
21692        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21693            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
21694        });
21695        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
21696        assert_eq!(row, 8, "Should find task when cursor is on function name");
21697    });
21698}
21699
21700#[gpui::test]
21701async fn test_folding_buffers(cx: &mut TestAppContext) {
21702    init_test(cx, |_| {});
21703
21704    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
21705    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
21706    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
21707
21708    let fs = FakeFs::new(cx.executor());
21709    fs.insert_tree(
21710        path!("/a"),
21711        json!({
21712            "first.rs": sample_text_1,
21713            "second.rs": sample_text_2,
21714            "third.rs": sample_text_3,
21715        }),
21716    )
21717    .await;
21718    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21719    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21720    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21721    let worktree = project.update(cx, |project, cx| {
21722        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
21723        assert_eq!(worktrees.len(), 1);
21724        worktrees.pop().unwrap()
21725    });
21726    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
21727
21728    let buffer_1 = project
21729        .update(cx, |project, cx| {
21730            project.open_buffer((worktree_id, "first.rs"), cx)
21731        })
21732        .await
21733        .unwrap();
21734    let buffer_2 = project
21735        .update(cx, |project, cx| {
21736            project.open_buffer((worktree_id, "second.rs"), cx)
21737        })
21738        .await
21739        .unwrap();
21740    let buffer_3 = project
21741        .update(cx, |project, cx| {
21742            project.open_buffer((worktree_id, "third.rs"), cx)
21743        })
21744        .await
21745        .unwrap();
21746
21747    let multi_buffer = cx.new(|cx| {
21748        let mut multi_buffer = MultiBuffer::new(ReadWrite);
21749        multi_buffer.push_excerpts(
21750            buffer_1.clone(),
21751            [
21752                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21753                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21754                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21755            ],
21756            cx,
21757        );
21758        multi_buffer.push_excerpts(
21759            buffer_2.clone(),
21760            [
21761                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21762                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21763                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21764            ],
21765            cx,
21766        );
21767        multi_buffer.push_excerpts(
21768            buffer_3.clone(),
21769            [
21770                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21771                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21772                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21773            ],
21774            cx,
21775        );
21776        multi_buffer
21777    });
21778    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
21779        Editor::new(
21780            EditorMode::full(),
21781            multi_buffer.clone(),
21782            Some(project.clone()),
21783            window,
21784            cx,
21785        )
21786    });
21787
21788    assert_eq!(
21789        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21790        "\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",
21791    );
21792
21793    multi_buffer_editor.update(cx, |editor, cx| {
21794        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
21795    });
21796    assert_eq!(
21797        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21798        "\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",
21799        "After folding the first buffer, its text should not be displayed"
21800    );
21801
21802    multi_buffer_editor.update(cx, |editor, cx| {
21803        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
21804    });
21805    assert_eq!(
21806        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21807        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
21808        "After folding the second buffer, its text should not be displayed"
21809    );
21810
21811    multi_buffer_editor.update(cx, |editor, cx| {
21812        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
21813    });
21814    assert_eq!(
21815        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21816        "\n\n\n\n\n",
21817        "After folding the third buffer, its text should not be displayed"
21818    );
21819
21820    // Emulate selection inside the fold logic, that should work
21821    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21822        editor
21823            .snapshot(window, cx)
21824            .next_line_boundary(Point::new(0, 4));
21825    });
21826
21827    multi_buffer_editor.update(cx, |editor, cx| {
21828        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
21829    });
21830    assert_eq!(
21831        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21832        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
21833        "After unfolding the second buffer, its text should be displayed"
21834    );
21835
21836    // Typing inside of buffer 1 causes that buffer to be unfolded.
21837    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21838        assert_eq!(
21839            multi_buffer
21840                .read(cx)
21841                .snapshot(cx)
21842                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
21843                .collect::<String>(),
21844            "bbbb"
21845        );
21846        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21847            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
21848        });
21849        editor.handle_input("B", window, cx);
21850    });
21851
21852    assert_eq!(
21853        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21854        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
21855        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
21856    );
21857
21858    multi_buffer_editor.update(cx, |editor, cx| {
21859        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
21860    });
21861    assert_eq!(
21862        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21863        "\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",
21864        "After unfolding the all buffers, all original text should be displayed"
21865    );
21866}
21867
21868#[gpui::test]
21869async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
21870    init_test(cx, |_| {});
21871
21872    let sample_text_1 = "1111\n2222\n3333".to_string();
21873    let sample_text_2 = "4444\n5555\n6666".to_string();
21874    let sample_text_3 = "7777\n8888\n9999".to_string();
21875
21876    let fs = FakeFs::new(cx.executor());
21877    fs.insert_tree(
21878        path!("/a"),
21879        json!({
21880            "first.rs": sample_text_1,
21881            "second.rs": sample_text_2,
21882            "third.rs": sample_text_3,
21883        }),
21884    )
21885    .await;
21886    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21887    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21888    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21889    let worktree = project.update(cx, |project, cx| {
21890        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
21891        assert_eq!(worktrees.len(), 1);
21892        worktrees.pop().unwrap()
21893    });
21894    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
21895
21896    let buffer_1 = project
21897        .update(cx, |project, cx| {
21898            project.open_buffer((worktree_id, "first.rs"), cx)
21899        })
21900        .await
21901        .unwrap();
21902    let buffer_2 = project
21903        .update(cx, |project, cx| {
21904            project.open_buffer((worktree_id, "second.rs"), cx)
21905        })
21906        .await
21907        .unwrap();
21908    let buffer_3 = project
21909        .update(cx, |project, cx| {
21910            project.open_buffer((worktree_id, "third.rs"), cx)
21911        })
21912        .await
21913        .unwrap();
21914
21915    let multi_buffer = cx.new(|cx| {
21916        let mut multi_buffer = MultiBuffer::new(ReadWrite);
21917        multi_buffer.push_excerpts(
21918            buffer_1.clone(),
21919            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21920            cx,
21921        );
21922        multi_buffer.push_excerpts(
21923            buffer_2.clone(),
21924            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21925            cx,
21926        );
21927        multi_buffer.push_excerpts(
21928            buffer_3.clone(),
21929            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21930            cx,
21931        );
21932        multi_buffer
21933    });
21934
21935    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
21936        Editor::new(
21937            EditorMode::full(),
21938            multi_buffer,
21939            Some(project.clone()),
21940            window,
21941            cx,
21942        )
21943    });
21944
21945    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
21946    assert_eq!(
21947        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21948        full_text,
21949    );
21950
21951    multi_buffer_editor.update(cx, |editor, cx| {
21952        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
21953    });
21954    assert_eq!(
21955        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21956        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
21957        "After folding the first buffer, its text should not be displayed"
21958    );
21959
21960    multi_buffer_editor.update(cx, |editor, cx| {
21961        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
21962    });
21963
21964    assert_eq!(
21965        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21966        "\n\n\n\n\n\n7777\n8888\n9999",
21967        "After folding the second buffer, its text should not be displayed"
21968    );
21969
21970    multi_buffer_editor.update(cx, |editor, cx| {
21971        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
21972    });
21973    assert_eq!(
21974        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21975        "\n\n\n\n\n",
21976        "After folding the third buffer, its text should not be displayed"
21977    );
21978
21979    multi_buffer_editor.update(cx, |editor, cx| {
21980        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
21981    });
21982    assert_eq!(
21983        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21984        "\n\n\n\n4444\n5555\n6666\n\n",
21985        "After unfolding the second buffer, its text should be displayed"
21986    );
21987
21988    multi_buffer_editor.update(cx, |editor, cx| {
21989        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
21990    });
21991    assert_eq!(
21992        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21993        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
21994        "After unfolding the first buffer, its text should be displayed"
21995    );
21996
21997    multi_buffer_editor.update(cx, |editor, cx| {
21998        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
21999    });
22000    assert_eq!(
22001        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22002        full_text,
22003        "After unfolding all buffers, all original text should be displayed"
22004    );
22005}
22006
22007#[gpui::test]
22008async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
22009    init_test(cx, |_| {});
22010
22011    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
22012
22013    let fs = FakeFs::new(cx.executor());
22014    fs.insert_tree(
22015        path!("/a"),
22016        json!({
22017            "main.rs": sample_text,
22018        }),
22019    )
22020    .await;
22021    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22022    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22023    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22024    let worktree = project.update(cx, |project, cx| {
22025        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22026        assert_eq!(worktrees.len(), 1);
22027        worktrees.pop().unwrap()
22028    });
22029    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22030
22031    let buffer_1 = project
22032        .update(cx, |project, cx| {
22033            project.open_buffer((worktree_id, "main.rs"), cx)
22034        })
22035        .await
22036        .unwrap();
22037
22038    let multi_buffer = cx.new(|cx| {
22039        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22040        multi_buffer.push_excerpts(
22041            buffer_1.clone(),
22042            [ExcerptRange::new(
22043                Point::new(0, 0)
22044                    ..Point::new(
22045                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
22046                        0,
22047                    ),
22048            )],
22049            cx,
22050        );
22051        multi_buffer
22052    });
22053    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22054        Editor::new(
22055            EditorMode::full(),
22056            multi_buffer,
22057            Some(project.clone()),
22058            window,
22059            cx,
22060        )
22061    });
22062
22063    let selection_range = Point::new(1, 0)..Point::new(2, 0);
22064    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22065        enum TestHighlight {}
22066        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
22067        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
22068        editor.highlight_text::<TestHighlight>(
22069            vec![highlight_range.clone()],
22070            HighlightStyle::color(Hsla::green()),
22071            cx,
22072        );
22073        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22074            s.select_ranges(Some(highlight_range))
22075        });
22076    });
22077
22078    let full_text = format!("\n\n{sample_text}");
22079    assert_eq!(
22080        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22081        full_text,
22082    );
22083}
22084
22085#[gpui::test]
22086async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
22087    init_test(cx, |_| {});
22088    cx.update(|cx| {
22089        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
22090            "keymaps/default-linux.json",
22091            cx,
22092        )
22093        .unwrap();
22094        cx.bind_keys(default_key_bindings);
22095    });
22096
22097    let (editor, cx) = cx.add_window_view(|window, cx| {
22098        let multi_buffer = MultiBuffer::build_multi(
22099            [
22100                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
22101                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
22102                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
22103                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
22104            ],
22105            cx,
22106        );
22107        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
22108
22109        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
22110        // fold all but the second buffer, so that we test navigating between two
22111        // adjacent folded buffers, as well as folded buffers at the start and
22112        // end the multibuffer
22113        editor.fold_buffer(buffer_ids[0], cx);
22114        editor.fold_buffer(buffer_ids[2], cx);
22115        editor.fold_buffer(buffer_ids[3], cx);
22116
22117        editor
22118    });
22119    cx.simulate_resize(size(px(1000.), px(1000.)));
22120
22121    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
22122    cx.assert_excerpts_with_selections(indoc! {"
22123        [EXCERPT]
22124        ˇ[FOLDED]
22125        [EXCERPT]
22126        a1
22127        b1
22128        [EXCERPT]
22129        [FOLDED]
22130        [EXCERPT]
22131        [FOLDED]
22132        "
22133    });
22134    cx.simulate_keystroke("down");
22135    cx.assert_excerpts_with_selections(indoc! {"
22136        [EXCERPT]
22137        [FOLDED]
22138        [EXCERPT]
22139        ˇa1
22140        b1
22141        [EXCERPT]
22142        [FOLDED]
22143        [EXCERPT]
22144        [FOLDED]
22145        "
22146    });
22147    cx.simulate_keystroke("down");
22148    cx.assert_excerpts_with_selections(indoc! {"
22149        [EXCERPT]
22150        [FOLDED]
22151        [EXCERPT]
22152        a1
22153        ˇb1
22154        [EXCERPT]
22155        [FOLDED]
22156        [EXCERPT]
22157        [FOLDED]
22158        "
22159    });
22160    cx.simulate_keystroke("down");
22161    cx.assert_excerpts_with_selections(indoc! {"
22162        [EXCERPT]
22163        [FOLDED]
22164        [EXCERPT]
22165        a1
22166        b1
22167        ˇ[EXCERPT]
22168        [FOLDED]
22169        [EXCERPT]
22170        [FOLDED]
22171        "
22172    });
22173    cx.simulate_keystroke("down");
22174    cx.assert_excerpts_with_selections(indoc! {"
22175        [EXCERPT]
22176        [FOLDED]
22177        [EXCERPT]
22178        a1
22179        b1
22180        [EXCERPT]
22181        ˇ[FOLDED]
22182        [EXCERPT]
22183        [FOLDED]
22184        "
22185    });
22186    for _ in 0..5 {
22187        cx.simulate_keystroke("down");
22188        cx.assert_excerpts_with_selections(indoc! {"
22189            [EXCERPT]
22190            [FOLDED]
22191            [EXCERPT]
22192            a1
22193            b1
22194            [EXCERPT]
22195            [FOLDED]
22196            [EXCERPT]
22197            ˇ[FOLDED]
22198            "
22199        });
22200    }
22201
22202    cx.simulate_keystroke("up");
22203    cx.assert_excerpts_with_selections(indoc! {"
22204        [EXCERPT]
22205        [FOLDED]
22206        [EXCERPT]
22207        a1
22208        b1
22209        [EXCERPT]
22210        ˇ[FOLDED]
22211        [EXCERPT]
22212        [FOLDED]
22213        "
22214    });
22215    cx.simulate_keystroke("up");
22216    cx.assert_excerpts_with_selections(indoc! {"
22217        [EXCERPT]
22218        [FOLDED]
22219        [EXCERPT]
22220        a1
22221        b1
22222        ˇ[EXCERPT]
22223        [FOLDED]
22224        [EXCERPT]
22225        [FOLDED]
22226        "
22227    });
22228    cx.simulate_keystroke("up");
22229    cx.assert_excerpts_with_selections(indoc! {"
22230        [EXCERPT]
22231        [FOLDED]
22232        [EXCERPT]
22233        a1
22234        ˇb1
22235        [EXCERPT]
22236        [FOLDED]
22237        [EXCERPT]
22238        [FOLDED]
22239        "
22240    });
22241    cx.simulate_keystroke("up");
22242    cx.assert_excerpts_with_selections(indoc! {"
22243        [EXCERPT]
22244        [FOLDED]
22245        [EXCERPT]
22246        ˇa1
22247        b1
22248        [EXCERPT]
22249        [FOLDED]
22250        [EXCERPT]
22251        [FOLDED]
22252        "
22253    });
22254    for _ in 0..5 {
22255        cx.simulate_keystroke("up");
22256        cx.assert_excerpts_with_selections(indoc! {"
22257            [EXCERPT]
22258            ˇ[FOLDED]
22259            [EXCERPT]
22260            a1
22261            b1
22262            [EXCERPT]
22263            [FOLDED]
22264            [EXCERPT]
22265            [FOLDED]
22266            "
22267        });
22268    }
22269}
22270
22271#[gpui::test]
22272async fn test_edit_prediction_text(cx: &mut TestAppContext) {
22273    init_test(cx, |_| {});
22274
22275    // Simple insertion
22276    assert_highlighted_edits(
22277        "Hello, world!",
22278        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
22279        true,
22280        cx,
22281        |highlighted_edits, cx| {
22282            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
22283            assert_eq!(highlighted_edits.highlights.len(), 1);
22284            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
22285            assert_eq!(
22286                highlighted_edits.highlights[0].1.background_color,
22287                Some(cx.theme().status().created_background)
22288            );
22289        },
22290    )
22291    .await;
22292
22293    // Replacement
22294    assert_highlighted_edits(
22295        "This is a test.",
22296        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
22297        false,
22298        cx,
22299        |highlighted_edits, cx| {
22300            assert_eq!(highlighted_edits.text, "That is a test.");
22301            assert_eq!(highlighted_edits.highlights.len(), 1);
22302            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
22303            assert_eq!(
22304                highlighted_edits.highlights[0].1.background_color,
22305                Some(cx.theme().status().created_background)
22306            );
22307        },
22308    )
22309    .await;
22310
22311    // Multiple edits
22312    assert_highlighted_edits(
22313        "Hello, world!",
22314        vec![
22315            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
22316            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
22317        ],
22318        false,
22319        cx,
22320        |highlighted_edits, cx| {
22321            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
22322            assert_eq!(highlighted_edits.highlights.len(), 2);
22323            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
22324            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
22325            assert_eq!(
22326                highlighted_edits.highlights[0].1.background_color,
22327                Some(cx.theme().status().created_background)
22328            );
22329            assert_eq!(
22330                highlighted_edits.highlights[1].1.background_color,
22331                Some(cx.theme().status().created_background)
22332            );
22333        },
22334    )
22335    .await;
22336
22337    // Multiple lines with edits
22338    assert_highlighted_edits(
22339        "First line\nSecond line\nThird line\nFourth line",
22340        vec![
22341            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
22342            (
22343                Point::new(2, 0)..Point::new(2, 10),
22344                "New third line".to_string(),
22345            ),
22346            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
22347        ],
22348        false,
22349        cx,
22350        |highlighted_edits, cx| {
22351            assert_eq!(
22352                highlighted_edits.text,
22353                "Second modified\nNew third line\nFourth updated line"
22354            );
22355            assert_eq!(highlighted_edits.highlights.len(), 3);
22356            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
22357            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
22358            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
22359            for highlight in &highlighted_edits.highlights {
22360                assert_eq!(
22361                    highlight.1.background_color,
22362                    Some(cx.theme().status().created_background)
22363                );
22364            }
22365        },
22366    )
22367    .await;
22368}
22369
22370#[gpui::test]
22371async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
22372    init_test(cx, |_| {});
22373
22374    // Deletion
22375    assert_highlighted_edits(
22376        "Hello, world!",
22377        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
22378        true,
22379        cx,
22380        |highlighted_edits, cx| {
22381            assert_eq!(highlighted_edits.text, "Hello, world!");
22382            assert_eq!(highlighted_edits.highlights.len(), 1);
22383            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
22384            assert_eq!(
22385                highlighted_edits.highlights[0].1.background_color,
22386                Some(cx.theme().status().deleted_background)
22387            );
22388        },
22389    )
22390    .await;
22391
22392    // Insertion
22393    assert_highlighted_edits(
22394        "Hello, world!",
22395        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
22396        true,
22397        cx,
22398        |highlighted_edits, cx| {
22399            assert_eq!(highlighted_edits.highlights.len(), 1);
22400            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
22401            assert_eq!(
22402                highlighted_edits.highlights[0].1.background_color,
22403                Some(cx.theme().status().created_background)
22404            );
22405        },
22406    )
22407    .await;
22408}
22409
22410async fn assert_highlighted_edits(
22411    text: &str,
22412    edits: Vec<(Range<Point>, String)>,
22413    include_deletions: bool,
22414    cx: &mut TestAppContext,
22415    assertion_fn: impl Fn(HighlightedText, &App),
22416) {
22417    let window = cx.add_window(|window, cx| {
22418        let buffer = MultiBuffer::build_simple(text, cx);
22419        Editor::new(EditorMode::full(), buffer, None, window, cx)
22420    });
22421    let cx = &mut VisualTestContext::from_window(*window, cx);
22422
22423    let (buffer, snapshot) = window
22424        .update(cx, |editor, _window, cx| {
22425            (
22426                editor.buffer().clone(),
22427                editor.buffer().read(cx).snapshot(cx),
22428            )
22429        })
22430        .unwrap();
22431
22432    let edits = edits
22433        .into_iter()
22434        .map(|(range, edit)| {
22435            (
22436                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
22437                edit,
22438            )
22439        })
22440        .collect::<Vec<_>>();
22441
22442    let text_anchor_edits = edits
22443        .clone()
22444        .into_iter()
22445        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
22446        .collect::<Vec<_>>();
22447
22448    let edit_preview = window
22449        .update(cx, |_, _window, cx| {
22450            buffer
22451                .read(cx)
22452                .as_singleton()
22453                .unwrap()
22454                .read(cx)
22455                .preview_edits(text_anchor_edits.into(), cx)
22456        })
22457        .unwrap()
22458        .await;
22459
22460    cx.update(|_window, cx| {
22461        let highlighted_edits = edit_prediction_edit_text(
22462            snapshot.as_singleton().unwrap().2,
22463            &edits,
22464            &edit_preview,
22465            include_deletions,
22466            cx,
22467        );
22468        assertion_fn(highlighted_edits, cx)
22469    });
22470}
22471
22472#[track_caller]
22473fn assert_breakpoint(
22474    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
22475    path: &Arc<Path>,
22476    expected: Vec<(u32, Breakpoint)>,
22477) {
22478    if expected.is_empty() {
22479        assert!(!breakpoints.contains_key(path), "{}", path.display());
22480    } else {
22481        let mut breakpoint = breakpoints
22482            .get(path)
22483            .unwrap()
22484            .iter()
22485            .map(|breakpoint| {
22486                (
22487                    breakpoint.row,
22488                    Breakpoint {
22489                        message: breakpoint.message.clone(),
22490                        state: breakpoint.state,
22491                        condition: breakpoint.condition.clone(),
22492                        hit_condition: breakpoint.hit_condition.clone(),
22493                    },
22494                )
22495            })
22496            .collect::<Vec<_>>();
22497
22498        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
22499
22500        assert_eq!(expected, breakpoint);
22501    }
22502}
22503
22504fn add_log_breakpoint_at_cursor(
22505    editor: &mut Editor,
22506    log_message: &str,
22507    window: &mut Window,
22508    cx: &mut Context<Editor>,
22509) {
22510    let (anchor, bp) = editor
22511        .breakpoints_at_cursors(window, cx)
22512        .first()
22513        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
22514        .unwrap_or_else(|| {
22515            let cursor_position: Point = editor.selections.newest(cx).head();
22516
22517            let breakpoint_position = editor
22518                .snapshot(window, cx)
22519                .display_snapshot
22520                .buffer_snapshot
22521                .anchor_before(Point::new(cursor_position.row, 0));
22522
22523            (breakpoint_position, Breakpoint::new_log(log_message))
22524        });
22525
22526    editor.edit_breakpoint_at_anchor(
22527        anchor,
22528        bp,
22529        BreakpointEditAction::EditLogMessage(log_message.into()),
22530        cx,
22531    );
22532}
22533
22534#[gpui::test]
22535async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
22536    init_test(cx, |_| {});
22537
22538    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22539    let fs = FakeFs::new(cx.executor());
22540    fs.insert_tree(
22541        path!("/a"),
22542        json!({
22543            "main.rs": sample_text,
22544        }),
22545    )
22546    .await;
22547    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22548    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22549    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22550
22551    let fs = FakeFs::new(cx.executor());
22552    fs.insert_tree(
22553        path!("/a"),
22554        json!({
22555            "main.rs": sample_text,
22556        }),
22557    )
22558    .await;
22559    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22560    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22561    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22562    let worktree_id = workspace
22563        .update(cx, |workspace, _window, cx| {
22564            workspace.project().update(cx, |project, cx| {
22565                project.worktrees(cx).next().unwrap().read(cx).id()
22566            })
22567        })
22568        .unwrap();
22569
22570    let buffer = project
22571        .update(cx, |project, cx| {
22572            project.open_buffer((worktree_id, "main.rs"), cx)
22573        })
22574        .await
22575        .unwrap();
22576
22577    let (editor, cx) = cx.add_window_view(|window, cx| {
22578        Editor::new(
22579            EditorMode::full(),
22580            MultiBuffer::build_from_buffer(buffer, cx),
22581            Some(project.clone()),
22582            window,
22583            cx,
22584        )
22585    });
22586
22587    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22588    let abs_path = project.read_with(cx, |project, cx| {
22589        project
22590            .absolute_path(&project_path, cx)
22591            .map(Arc::from)
22592            .unwrap()
22593    });
22594
22595    // assert we can add breakpoint on the first line
22596    editor.update_in(cx, |editor, window, cx| {
22597        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22598        editor.move_to_end(&MoveToEnd, window, cx);
22599        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22600    });
22601
22602    let breakpoints = editor.update(cx, |editor, cx| {
22603        editor
22604            .breakpoint_store()
22605            .as_ref()
22606            .unwrap()
22607            .read(cx)
22608            .all_source_breakpoints(cx)
22609    });
22610
22611    assert_eq!(1, breakpoints.len());
22612    assert_breakpoint(
22613        &breakpoints,
22614        &abs_path,
22615        vec![
22616            (0, Breakpoint::new_standard()),
22617            (3, Breakpoint::new_standard()),
22618        ],
22619    );
22620
22621    editor.update_in(cx, |editor, window, cx| {
22622        editor.move_to_beginning(&MoveToBeginning, window, cx);
22623        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22624    });
22625
22626    let breakpoints = editor.update(cx, |editor, cx| {
22627        editor
22628            .breakpoint_store()
22629            .as_ref()
22630            .unwrap()
22631            .read(cx)
22632            .all_source_breakpoints(cx)
22633    });
22634
22635    assert_eq!(1, breakpoints.len());
22636    assert_breakpoint(
22637        &breakpoints,
22638        &abs_path,
22639        vec![(3, Breakpoint::new_standard())],
22640    );
22641
22642    editor.update_in(cx, |editor, window, cx| {
22643        editor.move_to_end(&MoveToEnd, window, cx);
22644        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22645    });
22646
22647    let breakpoints = editor.update(cx, |editor, cx| {
22648        editor
22649            .breakpoint_store()
22650            .as_ref()
22651            .unwrap()
22652            .read(cx)
22653            .all_source_breakpoints(cx)
22654    });
22655
22656    assert_eq!(0, breakpoints.len());
22657    assert_breakpoint(&breakpoints, &abs_path, vec![]);
22658}
22659
22660#[gpui::test]
22661async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
22662    init_test(cx, |_| {});
22663
22664    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22665
22666    let fs = FakeFs::new(cx.executor());
22667    fs.insert_tree(
22668        path!("/a"),
22669        json!({
22670            "main.rs": sample_text,
22671        }),
22672    )
22673    .await;
22674    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22675    let (workspace, cx) =
22676        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22677
22678    let worktree_id = workspace.update(cx, |workspace, cx| {
22679        workspace.project().update(cx, |project, cx| {
22680            project.worktrees(cx).next().unwrap().read(cx).id()
22681        })
22682    });
22683
22684    let buffer = project
22685        .update(cx, |project, cx| {
22686            project.open_buffer((worktree_id, "main.rs"), cx)
22687        })
22688        .await
22689        .unwrap();
22690
22691    let (editor, cx) = cx.add_window_view(|window, cx| {
22692        Editor::new(
22693            EditorMode::full(),
22694            MultiBuffer::build_from_buffer(buffer, cx),
22695            Some(project.clone()),
22696            window,
22697            cx,
22698        )
22699    });
22700
22701    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22702    let abs_path = project.read_with(cx, |project, cx| {
22703        project
22704            .absolute_path(&project_path, cx)
22705            .map(Arc::from)
22706            .unwrap()
22707    });
22708
22709    editor.update_in(cx, |editor, window, cx| {
22710        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
22711    });
22712
22713    let breakpoints = editor.update(cx, |editor, cx| {
22714        editor
22715            .breakpoint_store()
22716            .as_ref()
22717            .unwrap()
22718            .read(cx)
22719            .all_source_breakpoints(cx)
22720    });
22721
22722    assert_breakpoint(
22723        &breakpoints,
22724        &abs_path,
22725        vec![(0, Breakpoint::new_log("hello world"))],
22726    );
22727
22728    // Removing a log message from a log breakpoint should remove it
22729    editor.update_in(cx, |editor, window, cx| {
22730        add_log_breakpoint_at_cursor(editor, "", window, cx);
22731    });
22732
22733    let breakpoints = editor.update(cx, |editor, cx| {
22734        editor
22735            .breakpoint_store()
22736            .as_ref()
22737            .unwrap()
22738            .read(cx)
22739            .all_source_breakpoints(cx)
22740    });
22741
22742    assert_breakpoint(&breakpoints, &abs_path, vec![]);
22743
22744    editor.update_in(cx, |editor, window, cx| {
22745        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22746        editor.move_to_end(&MoveToEnd, window, cx);
22747        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22748        // Not adding a log message to a standard breakpoint shouldn't remove it
22749        add_log_breakpoint_at_cursor(editor, "", window, cx);
22750    });
22751
22752    let breakpoints = editor.update(cx, |editor, cx| {
22753        editor
22754            .breakpoint_store()
22755            .as_ref()
22756            .unwrap()
22757            .read(cx)
22758            .all_source_breakpoints(cx)
22759    });
22760
22761    assert_breakpoint(
22762        &breakpoints,
22763        &abs_path,
22764        vec![
22765            (0, Breakpoint::new_standard()),
22766            (3, Breakpoint::new_standard()),
22767        ],
22768    );
22769
22770    editor.update_in(cx, |editor, window, cx| {
22771        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
22772    });
22773
22774    let breakpoints = editor.update(cx, |editor, cx| {
22775        editor
22776            .breakpoint_store()
22777            .as_ref()
22778            .unwrap()
22779            .read(cx)
22780            .all_source_breakpoints(cx)
22781    });
22782
22783    assert_breakpoint(
22784        &breakpoints,
22785        &abs_path,
22786        vec![
22787            (0, Breakpoint::new_standard()),
22788            (3, Breakpoint::new_log("hello world")),
22789        ],
22790    );
22791
22792    editor.update_in(cx, |editor, window, cx| {
22793        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
22794    });
22795
22796    let breakpoints = editor.update(cx, |editor, cx| {
22797        editor
22798            .breakpoint_store()
22799            .as_ref()
22800            .unwrap()
22801            .read(cx)
22802            .all_source_breakpoints(cx)
22803    });
22804
22805    assert_breakpoint(
22806        &breakpoints,
22807        &abs_path,
22808        vec![
22809            (0, Breakpoint::new_standard()),
22810            (3, Breakpoint::new_log("hello Earth!!")),
22811        ],
22812    );
22813}
22814
22815/// This also tests that Editor::breakpoint_at_cursor_head is working properly
22816/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
22817/// or when breakpoints were placed out of order. This tests for a regression too
22818#[gpui::test]
22819async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
22820    init_test(cx, |_| {});
22821
22822    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22823    let fs = FakeFs::new(cx.executor());
22824    fs.insert_tree(
22825        path!("/a"),
22826        json!({
22827            "main.rs": sample_text,
22828        }),
22829    )
22830    .await;
22831    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22832    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22833    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22834
22835    let fs = FakeFs::new(cx.executor());
22836    fs.insert_tree(
22837        path!("/a"),
22838        json!({
22839            "main.rs": sample_text,
22840        }),
22841    )
22842    .await;
22843    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22844    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22845    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22846    let worktree_id = workspace
22847        .update(cx, |workspace, _window, cx| {
22848            workspace.project().update(cx, |project, cx| {
22849                project.worktrees(cx).next().unwrap().read(cx).id()
22850            })
22851        })
22852        .unwrap();
22853
22854    let buffer = project
22855        .update(cx, |project, cx| {
22856            project.open_buffer((worktree_id, "main.rs"), cx)
22857        })
22858        .await
22859        .unwrap();
22860
22861    let (editor, cx) = cx.add_window_view(|window, cx| {
22862        Editor::new(
22863            EditorMode::full(),
22864            MultiBuffer::build_from_buffer(buffer, cx),
22865            Some(project.clone()),
22866            window,
22867            cx,
22868        )
22869    });
22870
22871    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22872    let abs_path = project.read_with(cx, |project, cx| {
22873        project
22874            .absolute_path(&project_path, cx)
22875            .map(Arc::from)
22876            .unwrap()
22877    });
22878
22879    // assert we can add breakpoint on the first line
22880    editor.update_in(cx, |editor, window, cx| {
22881        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22882        editor.move_to_end(&MoveToEnd, window, cx);
22883        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22884        editor.move_up(&MoveUp, window, cx);
22885        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22886    });
22887
22888    let breakpoints = editor.update(cx, |editor, cx| {
22889        editor
22890            .breakpoint_store()
22891            .as_ref()
22892            .unwrap()
22893            .read(cx)
22894            .all_source_breakpoints(cx)
22895    });
22896
22897    assert_eq!(1, breakpoints.len());
22898    assert_breakpoint(
22899        &breakpoints,
22900        &abs_path,
22901        vec![
22902            (0, Breakpoint::new_standard()),
22903            (2, Breakpoint::new_standard()),
22904            (3, Breakpoint::new_standard()),
22905        ],
22906    );
22907
22908    editor.update_in(cx, |editor, window, cx| {
22909        editor.move_to_beginning(&MoveToBeginning, window, cx);
22910        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22911        editor.move_to_end(&MoveToEnd, window, cx);
22912        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22913        // Disabling a breakpoint that doesn't exist should do nothing
22914        editor.move_up(&MoveUp, window, cx);
22915        editor.move_up(&MoveUp, window, cx);
22916        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22917    });
22918
22919    let breakpoints = editor.update(cx, |editor, cx| {
22920        editor
22921            .breakpoint_store()
22922            .as_ref()
22923            .unwrap()
22924            .read(cx)
22925            .all_source_breakpoints(cx)
22926    });
22927
22928    let disable_breakpoint = {
22929        let mut bp = Breakpoint::new_standard();
22930        bp.state = BreakpointState::Disabled;
22931        bp
22932    };
22933
22934    assert_eq!(1, breakpoints.len());
22935    assert_breakpoint(
22936        &breakpoints,
22937        &abs_path,
22938        vec![
22939            (0, disable_breakpoint.clone()),
22940            (2, Breakpoint::new_standard()),
22941            (3, disable_breakpoint.clone()),
22942        ],
22943    );
22944
22945    editor.update_in(cx, |editor, window, cx| {
22946        editor.move_to_beginning(&MoveToBeginning, window, cx);
22947        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
22948        editor.move_to_end(&MoveToEnd, window, cx);
22949        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
22950        editor.move_up(&MoveUp, window, cx);
22951        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22952    });
22953
22954    let breakpoints = editor.update(cx, |editor, cx| {
22955        editor
22956            .breakpoint_store()
22957            .as_ref()
22958            .unwrap()
22959            .read(cx)
22960            .all_source_breakpoints(cx)
22961    });
22962
22963    assert_eq!(1, breakpoints.len());
22964    assert_breakpoint(
22965        &breakpoints,
22966        &abs_path,
22967        vec![
22968            (0, Breakpoint::new_standard()),
22969            (2, disable_breakpoint),
22970            (3, Breakpoint::new_standard()),
22971        ],
22972    );
22973}
22974
22975#[gpui::test]
22976async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
22977    init_test(cx, |_| {});
22978    let capabilities = lsp::ServerCapabilities {
22979        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
22980            prepare_provider: Some(true),
22981            work_done_progress_options: Default::default(),
22982        })),
22983        ..Default::default()
22984    };
22985    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
22986
22987    cx.set_state(indoc! {"
22988        struct Fˇoo {}
22989    "});
22990
22991    cx.update_editor(|editor, _, cx| {
22992        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
22993        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
22994        editor.highlight_background::<DocumentHighlightRead>(
22995            &[highlight_range],
22996            |theme| theme.colors().editor_document_highlight_read_background,
22997            cx,
22998        );
22999    });
23000
23001    let mut prepare_rename_handler = cx
23002        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
23003            move |_, _, _| async move {
23004                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
23005                    start: lsp::Position {
23006                        line: 0,
23007                        character: 7,
23008                    },
23009                    end: lsp::Position {
23010                        line: 0,
23011                        character: 10,
23012                    },
23013                })))
23014            },
23015        );
23016    let prepare_rename_task = cx
23017        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23018        .expect("Prepare rename was not started");
23019    prepare_rename_handler.next().await.unwrap();
23020    prepare_rename_task.await.expect("Prepare rename failed");
23021
23022    let mut rename_handler =
23023        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23024            let edit = lsp::TextEdit {
23025                range: lsp::Range {
23026                    start: lsp::Position {
23027                        line: 0,
23028                        character: 7,
23029                    },
23030                    end: lsp::Position {
23031                        line: 0,
23032                        character: 10,
23033                    },
23034                },
23035                new_text: "FooRenamed".to_string(),
23036            };
23037            Ok(Some(lsp::WorkspaceEdit::new(
23038                // Specify the same edit twice
23039                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
23040            )))
23041        });
23042    let rename_task = cx
23043        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23044        .expect("Confirm rename was not started");
23045    rename_handler.next().await.unwrap();
23046    rename_task.await.expect("Confirm rename failed");
23047    cx.run_until_parked();
23048
23049    // Despite two edits, only one is actually applied as those are identical
23050    cx.assert_editor_state(indoc! {"
23051        struct FooRenamedˇ {}
23052    "});
23053}
23054
23055#[gpui::test]
23056async fn test_rename_without_prepare(cx: &mut TestAppContext) {
23057    init_test(cx, |_| {});
23058    // These capabilities indicate that the server does not support prepare rename.
23059    let capabilities = lsp::ServerCapabilities {
23060        rename_provider: Some(lsp::OneOf::Left(true)),
23061        ..Default::default()
23062    };
23063    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
23064
23065    cx.set_state(indoc! {"
23066        struct Fˇoo {}
23067    "});
23068
23069    cx.update_editor(|editor, _window, cx| {
23070        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
23071        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
23072        editor.highlight_background::<DocumentHighlightRead>(
23073            &[highlight_range],
23074            |theme| theme.colors().editor_document_highlight_read_background,
23075            cx,
23076        );
23077    });
23078
23079    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23080        .expect("Prepare rename was not started")
23081        .await
23082        .expect("Prepare rename failed");
23083
23084    let mut rename_handler =
23085        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23086            let edit = lsp::TextEdit {
23087                range: lsp::Range {
23088                    start: lsp::Position {
23089                        line: 0,
23090                        character: 7,
23091                    },
23092                    end: lsp::Position {
23093                        line: 0,
23094                        character: 10,
23095                    },
23096                },
23097                new_text: "FooRenamed".to_string(),
23098            };
23099            Ok(Some(lsp::WorkspaceEdit::new(
23100                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
23101            )))
23102        });
23103    let rename_task = cx
23104        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23105        .expect("Confirm rename was not started");
23106    rename_handler.next().await.unwrap();
23107    rename_task.await.expect("Confirm rename failed");
23108    cx.run_until_parked();
23109
23110    // Correct range is renamed, as `surrounding_word` is used to find it.
23111    cx.assert_editor_state(indoc! {"
23112        struct FooRenamedˇ {}
23113    "});
23114}
23115
23116#[gpui::test]
23117async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
23118    init_test(cx, |_| {});
23119    let mut cx = EditorTestContext::new(cx).await;
23120
23121    let language = Arc::new(
23122        Language::new(
23123            LanguageConfig::default(),
23124            Some(tree_sitter_html::LANGUAGE.into()),
23125        )
23126        .with_brackets_query(
23127            r#"
23128            ("<" @open "/>" @close)
23129            ("</" @open ">" @close)
23130            ("<" @open ">" @close)
23131            ("\"" @open "\"" @close)
23132            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
23133        "#,
23134        )
23135        .unwrap(),
23136    );
23137    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23138
23139    cx.set_state(indoc! {"
23140        <span>ˇ</span>
23141    "});
23142    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23143    cx.assert_editor_state(indoc! {"
23144        <span>
23145        ˇ
23146        </span>
23147    "});
23148
23149    cx.set_state(indoc! {"
23150        <span><span></span>ˇ</span>
23151    "});
23152    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23153    cx.assert_editor_state(indoc! {"
23154        <span><span></span>
23155        ˇ</span>
23156    "});
23157
23158    cx.set_state(indoc! {"
23159        <span>ˇ
23160        </span>
23161    "});
23162    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23163    cx.assert_editor_state(indoc! {"
23164        <span>
23165        ˇ
23166        </span>
23167    "});
23168}
23169
23170#[gpui::test(iterations = 10)]
23171async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
23172    init_test(cx, |_| {});
23173
23174    let fs = FakeFs::new(cx.executor());
23175    fs.insert_tree(
23176        path!("/dir"),
23177        json!({
23178            "a.ts": "a",
23179        }),
23180    )
23181    .await;
23182
23183    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
23184    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23185    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23186
23187    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23188    language_registry.add(Arc::new(Language::new(
23189        LanguageConfig {
23190            name: "TypeScript".into(),
23191            matcher: LanguageMatcher {
23192                path_suffixes: vec!["ts".to_string()],
23193                ..Default::default()
23194            },
23195            ..Default::default()
23196        },
23197        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
23198    )));
23199    let mut fake_language_servers = language_registry.register_fake_lsp(
23200        "TypeScript",
23201        FakeLspAdapter {
23202            capabilities: lsp::ServerCapabilities {
23203                code_lens_provider: Some(lsp::CodeLensOptions {
23204                    resolve_provider: Some(true),
23205                }),
23206                execute_command_provider: Some(lsp::ExecuteCommandOptions {
23207                    commands: vec!["_the/command".to_string()],
23208                    ..lsp::ExecuteCommandOptions::default()
23209                }),
23210                ..lsp::ServerCapabilities::default()
23211            },
23212            ..FakeLspAdapter::default()
23213        },
23214    );
23215
23216    let editor = workspace
23217        .update(cx, |workspace, window, cx| {
23218            workspace.open_abs_path(
23219                PathBuf::from(path!("/dir/a.ts")),
23220                OpenOptions::default(),
23221                window,
23222                cx,
23223            )
23224        })
23225        .unwrap()
23226        .await
23227        .unwrap()
23228        .downcast::<Editor>()
23229        .unwrap();
23230    cx.executor().run_until_parked();
23231
23232    let fake_server = fake_language_servers.next().await.unwrap();
23233
23234    let buffer = editor.update(cx, |editor, cx| {
23235        editor
23236            .buffer()
23237            .read(cx)
23238            .as_singleton()
23239            .expect("have opened a single file by path")
23240    });
23241
23242    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
23243    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
23244    drop(buffer_snapshot);
23245    let actions = cx
23246        .update_window(*workspace, |_, window, cx| {
23247            project.code_actions(&buffer, anchor..anchor, window, cx)
23248        })
23249        .unwrap();
23250
23251    fake_server
23252        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23253            Ok(Some(vec![
23254                lsp::CodeLens {
23255                    range: lsp::Range::default(),
23256                    command: Some(lsp::Command {
23257                        title: "Code lens command".to_owned(),
23258                        command: "_the/command".to_owned(),
23259                        arguments: None,
23260                    }),
23261                    data: None,
23262                },
23263                lsp::CodeLens {
23264                    range: lsp::Range::default(),
23265                    command: Some(lsp::Command {
23266                        title: "Command not in capabilities".to_owned(),
23267                        command: "not in capabilities".to_owned(),
23268                        arguments: None,
23269                    }),
23270                    data: None,
23271                },
23272                lsp::CodeLens {
23273                    range: lsp::Range {
23274                        start: lsp::Position {
23275                            line: 1,
23276                            character: 1,
23277                        },
23278                        end: lsp::Position {
23279                            line: 1,
23280                            character: 1,
23281                        },
23282                    },
23283                    command: Some(lsp::Command {
23284                        title: "Command not in range".to_owned(),
23285                        command: "_the/command".to_owned(),
23286                        arguments: None,
23287                    }),
23288                    data: None,
23289                },
23290            ]))
23291        })
23292        .next()
23293        .await;
23294
23295    let actions = actions.await.unwrap();
23296    assert_eq!(
23297        actions.len(),
23298        1,
23299        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
23300    );
23301    let action = actions[0].clone();
23302    let apply = project.update(cx, |project, cx| {
23303        project.apply_code_action(buffer.clone(), action, true, cx)
23304    });
23305
23306    // Resolving the code action does not populate its edits. In absence of
23307    // edits, we must execute the given command.
23308    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
23309        |mut lens, _| async move {
23310            let lens_command = lens.command.as_mut().expect("should have a command");
23311            assert_eq!(lens_command.title, "Code lens command");
23312            lens_command.arguments = Some(vec![json!("the-argument")]);
23313            Ok(lens)
23314        },
23315    );
23316
23317    // While executing the command, the language server sends the editor
23318    // a `workspaceEdit` request.
23319    fake_server
23320        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
23321            let fake = fake_server.clone();
23322            move |params, _| {
23323                assert_eq!(params.command, "_the/command");
23324                let fake = fake.clone();
23325                async move {
23326                    fake.server
23327                        .request::<lsp::request::ApplyWorkspaceEdit>(
23328                            lsp::ApplyWorkspaceEditParams {
23329                                label: None,
23330                                edit: lsp::WorkspaceEdit {
23331                                    changes: Some(
23332                                        [(
23333                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
23334                                            vec![lsp::TextEdit {
23335                                                range: lsp::Range::new(
23336                                                    lsp::Position::new(0, 0),
23337                                                    lsp::Position::new(0, 0),
23338                                                ),
23339                                                new_text: "X".into(),
23340                                            }],
23341                                        )]
23342                                        .into_iter()
23343                                        .collect(),
23344                                    ),
23345                                    ..lsp::WorkspaceEdit::default()
23346                                },
23347                            },
23348                        )
23349                        .await
23350                        .into_response()
23351                        .unwrap();
23352                    Ok(Some(json!(null)))
23353                }
23354            }
23355        })
23356        .next()
23357        .await;
23358
23359    // Applying the code lens command returns a project transaction containing the edits
23360    // sent by the language server in its `workspaceEdit` request.
23361    let transaction = apply.await.unwrap();
23362    assert!(transaction.0.contains_key(&buffer));
23363    buffer.update(cx, |buffer, cx| {
23364        assert_eq!(buffer.text(), "Xa");
23365        buffer.undo(cx);
23366        assert_eq!(buffer.text(), "a");
23367    });
23368
23369    let actions_after_edits = cx
23370        .update_window(*workspace, |_, window, cx| {
23371            project.code_actions(&buffer, anchor..anchor, window, cx)
23372        })
23373        .unwrap()
23374        .await
23375        .unwrap();
23376    assert_eq!(
23377        actions, actions_after_edits,
23378        "For the same selection, same code lens actions should be returned"
23379    );
23380
23381    let _responses =
23382        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23383            panic!("No more code lens requests are expected");
23384        });
23385    editor.update_in(cx, |editor, window, cx| {
23386        editor.select_all(&SelectAll, window, cx);
23387    });
23388    cx.executor().run_until_parked();
23389    let new_actions = cx
23390        .update_window(*workspace, |_, window, cx| {
23391            project.code_actions(&buffer, anchor..anchor, window, cx)
23392        })
23393        .unwrap()
23394        .await
23395        .unwrap();
23396    assert_eq!(
23397        actions, new_actions,
23398        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
23399    );
23400}
23401
23402#[gpui::test]
23403async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
23404    init_test(cx, |_| {});
23405
23406    let fs = FakeFs::new(cx.executor());
23407    let main_text = r#"fn main() {
23408println!("1");
23409println!("2");
23410println!("3");
23411println!("4");
23412println!("5");
23413}"#;
23414    let lib_text = "mod foo {}";
23415    fs.insert_tree(
23416        path!("/a"),
23417        json!({
23418            "lib.rs": lib_text,
23419            "main.rs": main_text,
23420        }),
23421    )
23422    .await;
23423
23424    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23425    let (workspace, cx) =
23426        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23427    let worktree_id = workspace.update(cx, |workspace, cx| {
23428        workspace.project().update(cx, |project, cx| {
23429            project.worktrees(cx).next().unwrap().read(cx).id()
23430        })
23431    });
23432
23433    let expected_ranges = vec![
23434        Point::new(0, 0)..Point::new(0, 0),
23435        Point::new(1, 0)..Point::new(1, 1),
23436        Point::new(2, 0)..Point::new(2, 2),
23437        Point::new(3, 0)..Point::new(3, 3),
23438    ];
23439
23440    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23441    let editor_1 = workspace
23442        .update_in(cx, |workspace, window, cx| {
23443            workspace.open_path(
23444                (worktree_id, "main.rs"),
23445                Some(pane_1.downgrade()),
23446                true,
23447                window,
23448                cx,
23449            )
23450        })
23451        .unwrap()
23452        .await
23453        .downcast::<Editor>()
23454        .unwrap();
23455    pane_1.update(cx, |pane, cx| {
23456        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23457        open_editor.update(cx, |editor, cx| {
23458            assert_eq!(
23459                editor.display_text(cx),
23460                main_text,
23461                "Original main.rs text on initial open",
23462            );
23463            assert_eq!(
23464                editor
23465                    .selections
23466                    .all::<Point>(cx)
23467                    .into_iter()
23468                    .map(|s| s.range())
23469                    .collect::<Vec<_>>(),
23470                vec![Point::zero()..Point::zero()],
23471                "Default selections on initial open",
23472            );
23473        })
23474    });
23475    editor_1.update_in(cx, |editor, window, cx| {
23476        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23477            s.select_ranges(expected_ranges.clone());
23478        });
23479    });
23480
23481    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
23482        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
23483    });
23484    let editor_2 = workspace
23485        .update_in(cx, |workspace, window, cx| {
23486            workspace.open_path(
23487                (worktree_id, "main.rs"),
23488                Some(pane_2.downgrade()),
23489                true,
23490                window,
23491                cx,
23492            )
23493        })
23494        .unwrap()
23495        .await
23496        .downcast::<Editor>()
23497        .unwrap();
23498    pane_2.update(cx, |pane, cx| {
23499        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23500        open_editor.update(cx, |editor, cx| {
23501            assert_eq!(
23502                editor.display_text(cx),
23503                main_text,
23504                "Original main.rs text on initial open in another panel",
23505            );
23506            assert_eq!(
23507                editor
23508                    .selections
23509                    .all::<Point>(cx)
23510                    .into_iter()
23511                    .map(|s| s.range())
23512                    .collect::<Vec<_>>(),
23513                vec![Point::zero()..Point::zero()],
23514                "Default selections on initial open in another panel",
23515            );
23516        })
23517    });
23518
23519    editor_2.update_in(cx, |editor, window, cx| {
23520        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
23521    });
23522
23523    let _other_editor_1 = workspace
23524        .update_in(cx, |workspace, window, cx| {
23525            workspace.open_path(
23526                (worktree_id, "lib.rs"),
23527                Some(pane_1.downgrade()),
23528                true,
23529                window,
23530                cx,
23531            )
23532        })
23533        .unwrap()
23534        .await
23535        .downcast::<Editor>()
23536        .unwrap();
23537    pane_1
23538        .update_in(cx, |pane, window, cx| {
23539            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23540        })
23541        .await
23542        .unwrap();
23543    drop(editor_1);
23544    pane_1.update(cx, |pane, cx| {
23545        pane.active_item()
23546            .unwrap()
23547            .downcast::<Editor>()
23548            .unwrap()
23549            .update(cx, |editor, cx| {
23550                assert_eq!(
23551                    editor.display_text(cx),
23552                    lib_text,
23553                    "Other file should be open and active",
23554                );
23555            });
23556        assert_eq!(pane.items().count(), 1, "No other editors should be open");
23557    });
23558
23559    let _other_editor_2 = workspace
23560        .update_in(cx, |workspace, window, cx| {
23561            workspace.open_path(
23562                (worktree_id, "lib.rs"),
23563                Some(pane_2.downgrade()),
23564                true,
23565                window,
23566                cx,
23567            )
23568        })
23569        .unwrap()
23570        .await
23571        .downcast::<Editor>()
23572        .unwrap();
23573    pane_2
23574        .update_in(cx, |pane, window, cx| {
23575            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23576        })
23577        .await
23578        .unwrap();
23579    drop(editor_2);
23580    pane_2.update(cx, |pane, cx| {
23581        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23582        open_editor.update(cx, |editor, cx| {
23583            assert_eq!(
23584                editor.display_text(cx),
23585                lib_text,
23586                "Other file should be open and active in another panel too",
23587            );
23588        });
23589        assert_eq!(
23590            pane.items().count(),
23591            1,
23592            "No other editors should be open in another pane",
23593        );
23594    });
23595
23596    let _editor_1_reopened = workspace
23597        .update_in(cx, |workspace, window, cx| {
23598            workspace.open_path(
23599                (worktree_id, "main.rs"),
23600                Some(pane_1.downgrade()),
23601                true,
23602                window,
23603                cx,
23604            )
23605        })
23606        .unwrap()
23607        .await
23608        .downcast::<Editor>()
23609        .unwrap();
23610    let _editor_2_reopened = workspace
23611        .update_in(cx, |workspace, window, cx| {
23612            workspace.open_path(
23613                (worktree_id, "main.rs"),
23614                Some(pane_2.downgrade()),
23615                true,
23616                window,
23617                cx,
23618            )
23619        })
23620        .unwrap()
23621        .await
23622        .downcast::<Editor>()
23623        .unwrap();
23624    pane_1.update(cx, |pane, cx| {
23625        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23626        open_editor.update(cx, |editor, cx| {
23627            assert_eq!(
23628                editor.display_text(cx),
23629                main_text,
23630                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
23631            );
23632            assert_eq!(
23633                editor
23634                    .selections
23635                    .all::<Point>(cx)
23636                    .into_iter()
23637                    .map(|s| s.range())
23638                    .collect::<Vec<_>>(),
23639                expected_ranges,
23640                "Previous editor in the 1st panel had selections and should get them restored on reopen",
23641            );
23642        })
23643    });
23644    pane_2.update(cx, |pane, cx| {
23645        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23646        open_editor.update(cx, |editor, cx| {
23647            assert_eq!(
23648                editor.display_text(cx),
23649                r#"fn main() {
23650⋯rintln!("1");
23651⋯intln!("2");
23652⋯ntln!("3");
23653println!("4");
23654println!("5");
23655}"#,
23656                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
23657            );
23658            assert_eq!(
23659                editor
23660                    .selections
23661                    .all::<Point>(cx)
23662                    .into_iter()
23663                    .map(|s| s.range())
23664                    .collect::<Vec<_>>(),
23665                vec![Point::zero()..Point::zero()],
23666                "Previous editor in the 2nd pane had no selections changed hence should restore none",
23667            );
23668        })
23669    });
23670}
23671
23672#[gpui::test]
23673async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
23674    init_test(cx, |_| {});
23675
23676    let fs = FakeFs::new(cx.executor());
23677    let main_text = r#"fn main() {
23678println!("1");
23679println!("2");
23680println!("3");
23681println!("4");
23682println!("5");
23683}"#;
23684    let lib_text = "mod foo {}";
23685    fs.insert_tree(
23686        path!("/a"),
23687        json!({
23688            "lib.rs": lib_text,
23689            "main.rs": main_text,
23690        }),
23691    )
23692    .await;
23693
23694    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23695    let (workspace, cx) =
23696        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23697    let worktree_id = workspace.update(cx, |workspace, cx| {
23698        workspace.project().update(cx, |project, cx| {
23699            project.worktrees(cx).next().unwrap().read(cx).id()
23700        })
23701    });
23702
23703    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23704    let editor = workspace
23705        .update_in(cx, |workspace, window, cx| {
23706            workspace.open_path(
23707                (worktree_id, "main.rs"),
23708                Some(pane.downgrade()),
23709                true,
23710                window,
23711                cx,
23712            )
23713        })
23714        .unwrap()
23715        .await
23716        .downcast::<Editor>()
23717        .unwrap();
23718    pane.update(cx, |pane, cx| {
23719        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23720        open_editor.update(cx, |editor, cx| {
23721            assert_eq!(
23722                editor.display_text(cx),
23723                main_text,
23724                "Original main.rs text on initial open",
23725            );
23726        })
23727    });
23728    editor.update_in(cx, |editor, window, cx| {
23729        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
23730    });
23731
23732    cx.update_global(|store: &mut SettingsStore, cx| {
23733        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
23734            s.restore_on_file_reopen = Some(false);
23735        });
23736    });
23737    editor.update_in(cx, |editor, window, cx| {
23738        editor.fold_ranges(
23739            vec![
23740                Point::new(1, 0)..Point::new(1, 1),
23741                Point::new(2, 0)..Point::new(2, 2),
23742                Point::new(3, 0)..Point::new(3, 3),
23743            ],
23744            false,
23745            window,
23746            cx,
23747        );
23748    });
23749    pane.update_in(cx, |pane, window, cx| {
23750        pane.close_all_items(&CloseAllItems::default(), window, cx)
23751    })
23752    .await
23753    .unwrap();
23754    pane.update(cx, |pane, _| {
23755        assert!(pane.active_item().is_none());
23756    });
23757    cx.update_global(|store: &mut SettingsStore, cx| {
23758        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
23759            s.restore_on_file_reopen = Some(true);
23760        });
23761    });
23762
23763    let _editor_reopened = workspace
23764        .update_in(cx, |workspace, window, cx| {
23765            workspace.open_path(
23766                (worktree_id, "main.rs"),
23767                Some(pane.downgrade()),
23768                true,
23769                window,
23770                cx,
23771            )
23772        })
23773        .unwrap()
23774        .await
23775        .downcast::<Editor>()
23776        .unwrap();
23777    pane.update(cx, |pane, cx| {
23778        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23779        open_editor.update(cx, |editor, cx| {
23780            assert_eq!(
23781                editor.display_text(cx),
23782                main_text,
23783                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
23784            );
23785        })
23786    });
23787}
23788
23789#[gpui::test]
23790async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
23791    struct EmptyModalView {
23792        focus_handle: gpui::FocusHandle,
23793    }
23794    impl EventEmitter<DismissEvent> for EmptyModalView {}
23795    impl Render for EmptyModalView {
23796        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
23797            div()
23798        }
23799    }
23800    impl Focusable for EmptyModalView {
23801        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
23802            self.focus_handle.clone()
23803        }
23804    }
23805    impl workspace::ModalView for EmptyModalView {}
23806    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
23807        EmptyModalView {
23808            focus_handle: cx.focus_handle(),
23809        }
23810    }
23811
23812    init_test(cx, |_| {});
23813
23814    let fs = FakeFs::new(cx.executor());
23815    let project = Project::test(fs, [], cx).await;
23816    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23817    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
23818    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23819    let editor = cx.new_window_entity(|window, cx| {
23820        Editor::new(
23821            EditorMode::full(),
23822            buffer,
23823            Some(project.clone()),
23824            window,
23825            cx,
23826        )
23827    });
23828    workspace
23829        .update(cx, |workspace, window, cx| {
23830            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
23831        })
23832        .unwrap();
23833    editor.update_in(cx, |editor, window, cx| {
23834        editor.open_context_menu(&OpenContextMenu, window, cx);
23835        assert!(editor.mouse_context_menu.is_some());
23836    });
23837    workspace
23838        .update(cx, |workspace, window, cx| {
23839            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
23840        })
23841        .unwrap();
23842    cx.read(|cx| {
23843        assert!(editor.read(cx).mouse_context_menu.is_none());
23844    });
23845}
23846
23847#[gpui::test]
23848async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
23849    init_test(cx, |_| {});
23850
23851    let fs = FakeFs::new(cx.executor());
23852    fs.insert_file(path!("/file.html"), Default::default())
23853        .await;
23854
23855    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
23856
23857    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23858    let html_language = Arc::new(Language::new(
23859        LanguageConfig {
23860            name: "HTML".into(),
23861            matcher: LanguageMatcher {
23862                path_suffixes: vec!["html".to_string()],
23863                ..LanguageMatcher::default()
23864            },
23865            brackets: BracketPairConfig {
23866                pairs: vec![BracketPair {
23867                    start: "<".into(),
23868                    end: ">".into(),
23869                    close: true,
23870                    ..Default::default()
23871                }],
23872                ..Default::default()
23873            },
23874            ..Default::default()
23875        },
23876        Some(tree_sitter_html::LANGUAGE.into()),
23877    ));
23878    language_registry.add(html_language);
23879    let mut fake_servers = language_registry.register_fake_lsp(
23880        "HTML",
23881        FakeLspAdapter {
23882            capabilities: lsp::ServerCapabilities {
23883                completion_provider: Some(lsp::CompletionOptions {
23884                    resolve_provider: Some(true),
23885                    ..Default::default()
23886                }),
23887                ..Default::default()
23888            },
23889            ..Default::default()
23890        },
23891    );
23892
23893    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23894    let cx = &mut VisualTestContext::from_window(*workspace, cx);
23895
23896    let worktree_id = workspace
23897        .update(cx, |workspace, _window, cx| {
23898            workspace.project().update(cx, |project, cx| {
23899                project.worktrees(cx).next().unwrap().read(cx).id()
23900            })
23901        })
23902        .unwrap();
23903    project
23904        .update(cx, |project, cx| {
23905            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
23906        })
23907        .await
23908        .unwrap();
23909    let editor = workspace
23910        .update(cx, |workspace, window, cx| {
23911            workspace.open_path((worktree_id, "file.html"), None, true, window, cx)
23912        })
23913        .unwrap()
23914        .await
23915        .unwrap()
23916        .downcast::<Editor>()
23917        .unwrap();
23918
23919    let fake_server = fake_servers.next().await.unwrap();
23920    editor.update_in(cx, |editor, window, cx| {
23921        editor.set_text("<ad></ad>", window, cx);
23922        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23923            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
23924        });
23925        let Some((buffer, _)) = editor
23926            .buffer
23927            .read(cx)
23928            .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
23929        else {
23930            panic!("Failed to get buffer for selection position");
23931        };
23932        let buffer = buffer.read(cx);
23933        let buffer_id = buffer.remote_id();
23934        let opening_range =
23935            buffer.anchor_before(Point::new(0, 1))..buffer.anchor_after(Point::new(0, 3));
23936        let closing_range =
23937            buffer.anchor_before(Point::new(0, 6))..buffer.anchor_after(Point::new(0, 8));
23938        let mut linked_ranges = HashMap::default();
23939        linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
23940        editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
23941    });
23942    let mut completion_handle =
23943        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
23944            Ok(Some(lsp::CompletionResponse::Array(vec![
23945                lsp::CompletionItem {
23946                    label: "head".to_string(),
23947                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23948                        lsp::InsertReplaceEdit {
23949                            new_text: "head".to_string(),
23950                            insert: lsp::Range::new(
23951                                lsp::Position::new(0, 1),
23952                                lsp::Position::new(0, 3),
23953                            ),
23954                            replace: lsp::Range::new(
23955                                lsp::Position::new(0, 1),
23956                                lsp::Position::new(0, 3),
23957                            ),
23958                        },
23959                    )),
23960                    ..Default::default()
23961                },
23962            ])))
23963        });
23964    editor.update_in(cx, |editor, window, cx| {
23965        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
23966    });
23967    cx.run_until_parked();
23968    completion_handle.next().await.unwrap();
23969    editor.update(cx, |editor, _| {
23970        assert!(
23971            editor.context_menu_visible(),
23972            "Completion menu should be visible"
23973        );
23974    });
23975    editor.update_in(cx, |editor, window, cx| {
23976        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
23977    });
23978    cx.executor().run_until_parked();
23979    editor.update(cx, |editor, cx| {
23980        assert_eq!(editor.text(cx), "<head></head>");
23981    });
23982}
23983
23984#[gpui::test]
23985async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
23986    init_test(cx, |_| {});
23987
23988    let fs = FakeFs::new(cx.executor());
23989    fs.insert_tree(
23990        path!("/root"),
23991        json!({
23992            "a": {
23993                "main.rs": "fn main() {}",
23994            },
23995            "foo": {
23996                "bar": {
23997                    "external_file.rs": "pub mod external {}",
23998                }
23999            }
24000        }),
24001    )
24002    .await;
24003
24004    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
24005    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24006    language_registry.add(rust_lang());
24007    let _fake_servers = language_registry.register_fake_lsp(
24008        "Rust",
24009        FakeLspAdapter {
24010            ..FakeLspAdapter::default()
24011        },
24012    );
24013    let (workspace, cx) =
24014        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24015    let worktree_id = workspace.update(cx, |workspace, cx| {
24016        workspace.project().update(cx, |project, cx| {
24017            project.worktrees(cx).next().unwrap().read(cx).id()
24018        })
24019    });
24020
24021    let assert_language_servers_count =
24022        |expected: usize, context: &str, cx: &mut VisualTestContext| {
24023            project.update(cx, |project, cx| {
24024                let current = project
24025                    .lsp_store()
24026                    .read(cx)
24027                    .as_local()
24028                    .unwrap()
24029                    .language_servers
24030                    .len();
24031                assert_eq!(expected, current, "{context}");
24032            });
24033        };
24034
24035    assert_language_servers_count(
24036        0,
24037        "No servers should be running before any file is open",
24038        cx,
24039    );
24040    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24041    let main_editor = workspace
24042        .update_in(cx, |workspace, window, cx| {
24043            workspace.open_path(
24044                (worktree_id, "main.rs"),
24045                Some(pane.downgrade()),
24046                true,
24047                window,
24048                cx,
24049            )
24050        })
24051        .unwrap()
24052        .await
24053        .downcast::<Editor>()
24054        .unwrap();
24055    pane.update(cx, |pane, cx| {
24056        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24057        open_editor.update(cx, |editor, cx| {
24058            assert_eq!(
24059                editor.display_text(cx),
24060                "fn main() {}",
24061                "Original main.rs text on initial open",
24062            );
24063        });
24064        assert_eq!(open_editor, main_editor);
24065    });
24066    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
24067
24068    let external_editor = workspace
24069        .update_in(cx, |workspace, window, cx| {
24070            workspace.open_abs_path(
24071                PathBuf::from("/root/foo/bar/external_file.rs"),
24072                OpenOptions::default(),
24073                window,
24074                cx,
24075            )
24076        })
24077        .await
24078        .expect("opening external file")
24079        .downcast::<Editor>()
24080        .expect("downcasted external file's open element to editor");
24081    pane.update(cx, |pane, cx| {
24082        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24083        open_editor.update(cx, |editor, cx| {
24084            assert_eq!(
24085                editor.display_text(cx),
24086                "pub mod external {}",
24087                "External file is open now",
24088            );
24089        });
24090        assert_eq!(open_editor, external_editor);
24091    });
24092    assert_language_servers_count(
24093        1,
24094        "Second, external, *.rs file should join the existing server",
24095        cx,
24096    );
24097
24098    pane.update_in(cx, |pane, window, cx| {
24099        pane.close_active_item(&CloseActiveItem::default(), window, cx)
24100    })
24101    .await
24102    .unwrap();
24103    pane.update_in(cx, |pane, window, cx| {
24104        pane.navigate_backward(&Default::default(), window, cx);
24105    });
24106    cx.run_until_parked();
24107    pane.update(cx, |pane, cx| {
24108        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24109        open_editor.update(cx, |editor, cx| {
24110            assert_eq!(
24111                editor.display_text(cx),
24112                "pub mod external {}",
24113                "External file is open now",
24114            );
24115        });
24116    });
24117    assert_language_servers_count(
24118        1,
24119        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
24120        cx,
24121    );
24122
24123    cx.update(|_, cx| {
24124        workspace::reload(cx);
24125    });
24126    assert_language_servers_count(
24127        1,
24128        "After reloading the worktree with local and external files opened, only one project should be started",
24129        cx,
24130    );
24131}
24132
24133#[gpui::test]
24134async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
24135    init_test(cx, |_| {});
24136
24137    let mut cx = EditorTestContext::new(cx).await;
24138    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24139    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24140
24141    // test cursor move to start of each line on tab
24142    // for `if`, `elif`, `else`, `while`, `with` and `for`
24143    cx.set_state(indoc! {"
24144        def main():
24145        ˇ    for item in items:
24146        ˇ        while item.active:
24147        ˇ            if item.value > 10:
24148        ˇ                continue
24149        ˇ            elif item.value < 0:
24150        ˇ                break
24151        ˇ            else:
24152        ˇ                with item.context() as ctx:
24153        ˇ                    yield count
24154        ˇ        else:
24155        ˇ            log('while else')
24156        ˇ    else:
24157        ˇ        log('for else')
24158    "});
24159    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24160    cx.assert_editor_state(indoc! {"
24161        def main():
24162            ˇfor item in items:
24163                ˇwhile item.active:
24164                    ˇif item.value > 10:
24165                        ˇcontinue
24166                    ˇelif item.value < 0:
24167                        ˇbreak
24168                    ˇelse:
24169                        ˇwith item.context() as ctx:
24170                            ˇyield count
24171                ˇelse:
24172                    ˇlog('while else')
24173            ˇelse:
24174                ˇlog('for else')
24175    "});
24176    // test relative indent is preserved when tab
24177    // for `if`, `elif`, `else`, `while`, `with` and `for`
24178    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24179    cx.assert_editor_state(indoc! {"
24180        def main():
24181                ˇfor item in items:
24182                    ˇwhile item.active:
24183                        ˇif item.value > 10:
24184                            ˇcontinue
24185                        ˇelif item.value < 0:
24186                            ˇbreak
24187                        ˇelse:
24188                            ˇwith item.context() as ctx:
24189                                ˇyield count
24190                    ˇelse:
24191                        ˇlog('while else')
24192                ˇelse:
24193                    ˇlog('for else')
24194    "});
24195
24196    // test cursor move to start of each line on tab
24197    // for `try`, `except`, `else`, `finally`, `match` and `def`
24198    cx.set_state(indoc! {"
24199        def main():
24200        ˇ    try:
24201        ˇ        fetch()
24202        ˇ    except ValueError:
24203        ˇ        handle_error()
24204        ˇ    else:
24205        ˇ        match value:
24206        ˇ            case _:
24207        ˇ    finally:
24208        ˇ        def status():
24209        ˇ            return 0
24210    "});
24211    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24212    cx.assert_editor_state(indoc! {"
24213        def main():
24214            ˇtry:
24215                ˇfetch()
24216            ˇexcept ValueError:
24217                ˇhandle_error()
24218            ˇelse:
24219                ˇmatch value:
24220                    ˇcase _:
24221            ˇfinally:
24222                ˇdef status():
24223                    ˇreturn 0
24224    "});
24225    // test relative indent is preserved when tab
24226    // for `try`, `except`, `else`, `finally`, `match` and `def`
24227    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24228    cx.assert_editor_state(indoc! {"
24229        def main():
24230                ˇtry:
24231                    ˇfetch()
24232                ˇexcept ValueError:
24233                    ˇhandle_error()
24234                ˇelse:
24235                    ˇmatch value:
24236                        ˇcase _:
24237                ˇfinally:
24238                    ˇdef status():
24239                        ˇreturn 0
24240    "});
24241}
24242
24243#[gpui::test]
24244async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
24245    init_test(cx, |_| {});
24246
24247    let mut cx = EditorTestContext::new(cx).await;
24248    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24249    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24250
24251    // test `else` auto outdents when typed inside `if` block
24252    cx.set_state(indoc! {"
24253        def main():
24254            if i == 2:
24255                return
24256                ˇ
24257    "});
24258    cx.update_editor(|editor, window, cx| {
24259        editor.handle_input("else:", window, cx);
24260    });
24261    cx.assert_editor_state(indoc! {"
24262        def main():
24263            if i == 2:
24264                return
24265            else:ˇ
24266    "});
24267
24268    // test `except` auto outdents when typed inside `try` block
24269    cx.set_state(indoc! {"
24270        def main():
24271            try:
24272                i = 2
24273                ˇ
24274    "});
24275    cx.update_editor(|editor, window, cx| {
24276        editor.handle_input("except:", window, cx);
24277    });
24278    cx.assert_editor_state(indoc! {"
24279        def main():
24280            try:
24281                i = 2
24282            except:ˇ
24283    "});
24284
24285    // test `else` auto outdents when typed inside `except` block
24286    cx.set_state(indoc! {"
24287        def main():
24288            try:
24289                i = 2
24290            except:
24291                j = 2
24292                ˇ
24293    "});
24294    cx.update_editor(|editor, window, cx| {
24295        editor.handle_input("else:", window, cx);
24296    });
24297    cx.assert_editor_state(indoc! {"
24298        def main():
24299            try:
24300                i = 2
24301            except:
24302                j = 2
24303            else:ˇ
24304    "});
24305
24306    // test `finally` auto outdents when typed inside `else` block
24307    cx.set_state(indoc! {"
24308        def main():
24309            try:
24310                i = 2
24311            except:
24312                j = 2
24313            else:
24314                k = 2
24315                ˇ
24316    "});
24317    cx.update_editor(|editor, window, cx| {
24318        editor.handle_input("finally:", window, cx);
24319    });
24320    cx.assert_editor_state(indoc! {"
24321        def main():
24322            try:
24323                i = 2
24324            except:
24325                j = 2
24326            else:
24327                k = 2
24328            finally:ˇ
24329    "});
24330
24331    // test `else` does not outdents when typed inside `except` block right after for block
24332    cx.set_state(indoc! {"
24333        def main():
24334            try:
24335                i = 2
24336            except:
24337                for i in range(n):
24338                    pass
24339                ˇ
24340    "});
24341    cx.update_editor(|editor, window, cx| {
24342        editor.handle_input("else:", window, cx);
24343    });
24344    cx.assert_editor_state(indoc! {"
24345        def main():
24346            try:
24347                i = 2
24348            except:
24349                for i in range(n):
24350                    pass
24351                else:ˇ
24352    "});
24353
24354    // test `finally` auto outdents when typed inside `else` block right after for block
24355    cx.set_state(indoc! {"
24356        def main():
24357            try:
24358                i = 2
24359            except:
24360                j = 2
24361            else:
24362                for i in range(n):
24363                    pass
24364                ˇ
24365    "});
24366    cx.update_editor(|editor, window, cx| {
24367        editor.handle_input("finally:", window, cx);
24368    });
24369    cx.assert_editor_state(indoc! {"
24370        def main():
24371            try:
24372                i = 2
24373            except:
24374                j = 2
24375            else:
24376                for i in range(n):
24377                    pass
24378            finally:ˇ
24379    "});
24380
24381    // test `except` outdents to inner "try" block
24382    cx.set_state(indoc! {"
24383        def main():
24384            try:
24385                i = 2
24386                if i == 2:
24387                    try:
24388                        i = 3
24389                        ˇ
24390    "});
24391    cx.update_editor(|editor, window, cx| {
24392        editor.handle_input("except:", window, cx);
24393    });
24394    cx.assert_editor_state(indoc! {"
24395        def main():
24396            try:
24397                i = 2
24398                if i == 2:
24399                    try:
24400                        i = 3
24401                    except:ˇ
24402    "});
24403
24404    // test `except` outdents to outer "try" block
24405    cx.set_state(indoc! {"
24406        def main():
24407            try:
24408                i = 2
24409                if i == 2:
24410                    try:
24411                        i = 3
24412                ˇ
24413    "});
24414    cx.update_editor(|editor, window, cx| {
24415        editor.handle_input("except:", window, cx);
24416    });
24417    cx.assert_editor_state(indoc! {"
24418        def main():
24419            try:
24420                i = 2
24421                if i == 2:
24422                    try:
24423                        i = 3
24424            except:ˇ
24425    "});
24426
24427    // test `else` stays at correct indent when typed after `for` block
24428    cx.set_state(indoc! {"
24429        def main():
24430            for i in range(10):
24431                if i == 3:
24432                    break
24433            ˇ
24434    "});
24435    cx.update_editor(|editor, window, cx| {
24436        editor.handle_input("else:", window, cx);
24437    });
24438    cx.assert_editor_state(indoc! {"
24439        def main():
24440            for i in range(10):
24441                if i == 3:
24442                    break
24443            else:ˇ
24444    "});
24445
24446    // test does not outdent on typing after line with square brackets
24447    cx.set_state(indoc! {"
24448        def f() -> list[str]:
24449            ˇ
24450    "});
24451    cx.update_editor(|editor, window, cx| {
24452        editor.handle_input("a", window, cx);
24453    });
24454    cx.assert_editor_state(indoc! {"
24455        def f() -> list[str]:
2445624457    "});
24458
24459    // test does not outdent on typing : after case keyword
24460    cx.set_state(indoc! {"
24461        match 1:
24462            caseˇ
24463    "});
24464    cx.update_editor(|editor, window, cx| {
24465        editor.handle_input(":", window, cx);
24466    });
24467    cx.assert_editor_state(indoc! {"
24468        match 1:
24469            case:ˇ
24470    "});
24471}
24472
24473#[gpui::test]
24474async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
24475    init_test(cx, |_| {});
24476    update_test_language_settings(cx, |settings| {
24477        settings.defaults.extend_comment_on_newline = Some(false);
24478    });
24479    let mut cx = EditorTestContext::new(cx).await;
24480    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24481    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24482
24483    // test correct indent after newline on comment
24484    cx.set_state(indoc! {"
24485        # COMMENT:ˇ
24486    "});
24487    cx.update_editor(|editor, window, cx| {
24488        editor.newline(&Newline, window, cx);
24489    });
24490    cx.assert_editor_state(indoc! {"
24491        # COMMENT:
24492        ˇ
24493    "});
24494
24495    // test correct indent after newline in brackets
24496    cx.set_state(indoc! {"
24497        {ˇ}
24498    "});
24499    cx.update_editor(|editor, window, cx| {
24500        editor.newline(&Newline, window, cx);
24501    });
24502    cx.run_until_parked();
24503    cx.assert_editor_state(indoc! {"
24504        {
24505            ˇ
24506        }
24507    "});
24508
24509    cx.set_state(indoc! {"
24510        (ˇ)
24511    "});
24512    cx.update_editor(|editor, window, cx| {
24513        editor.newline(&Newline, window, cx);
24514    });
24515    cx.run_until_parked();
24516    cx.assert_editor_state(indoc! {"
24517        (
24518            ˇ
24519        )
24520    "});
24521
24522    // do not indent after empty lists or dictionaries
24523    cx.set_state(indoc! {"
24524        a = []ˇ
24525    "});
24526    cx.update_editor(|editor, window, cx| {
24527        editor.newline(&Newline, window, cx);
24528    });
24529    cx.run_until_parked();
24530    cx.assert_editor_state(indoc! {"
24531        a = []
24532        ˇ
24533    "});
24534}
24535
24536#[gpui::test]
24537async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
24538    init_test(cx, |_| {});
24539
24540    let mut cx = EditorTestContext::new(cx).await;
24541    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24542    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24543
24544    // test cursor move to start of each line on tab
24545    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
24546    cx.set_state(indoc! {"
24547        function main() {
24548        ˇ    for item in $items; do
24549        ˇ        while [ -n \"$item\" ]; do
24550        ˇ            if [ \"$value\" -gt 10 ]; then
24551        ˇ                continue
24552        ˇ            elif [ \"$value\" -lt 0 ]; then
24553        ˇ                break
24554        ˇ            else
24555        ˇ                echo \"$item\"
24556        ˇ            fi
24557        ˇ        done
24558        ˇ    done
24559        ˇ}
24560    "});
24561    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24562    cx.assert_editor_state(indoc! {"
24563        function main() {
24564            ˇfor item in $items; do
24565                ˇwhile [ -n \"$item\" ]; do
24566                    ˇif [ \"$value\" -gt 10 ]; then
24567                        ˇcontinue
24568                    ˇelif [ \"$value\" -lt 0 ]; then
24569                        ˇbreak
24570                    ˇelse
24571                        ˇecho \"$item\"
24572                    ˇfi
24573                ˇdone
24574            ˇdone
24575        ˇ}
24576    "});
24577    // test relative indent is preserved when tab
24578    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24579    cx.assert_editor_state(indoc! {"
24580        function main() {
24581                ˇfor item in $items; do
24582                    ˇwhile [ -n \"$item\" ]; do
24583                        ˇif [ \"$value\" -gt 10 ]; then
24584                            ˇcontinue
24585                        ˇelif [ \"$value\" -lt 0 ]; then
24586                            ˇbreak
24587                        ˇelse
24588                            ˇecho \"$item\"
24589                        ˇfi
24590                    ˇdone
24591                ˇdone
24592            ˇ}
24593    "});
24594
24595    // test cursor move to start of each line on tab
24596    // for `case` statement with patterns
24597    cx.set_state(indoc! {"
24598        function handle() {
24599        ˇ    case \"$1\" in
24600        ˇ        start)
24601        ˇ            echo \"a\"
24602        ˇ            ;;
24603        ˇ        stop)
24604        ˇ            echo \"b\"
24605        ˇ            ;;
24606        ˇ        *)
24607        ˇ            echo \"c\"
24608        ˇ            ;;
24609        ˇ    esac
24610        ˇ}
24611    "});
24612    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24613    cx.assert_editor_state(indoc! {"
24614        function handle() {
24615            ˇcase \"$1\" in
24616                ˇstart)
24617                    ˇecho \"a\"
24618                    ˇ;;
24619                ˇstop)
24620                    ˇecho \"b\"
24621                    ˇ;;
24622                ˇ*)
24623                    ˇecho \"c\"
24624                    ˇ;;
24625            ˇesac
24626        ˇ}
24627    "});
24628}
24629
24630#[gpui::test]
24631async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
24632    init_test(cx, |_| {});
24633
24634    let mut cx = EditorTestContext::new(cx).await;
24635    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24636    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24637
24638    // test indents on comment insert
24639    cx.set_state(indoc! {"
24640        function main() {
24641        ˇ    for item in $items; do
24642        ˇ        while [ -n \"$item\" ]; do
24643        ˇ            if [ \"$value\" -gt 10 ]; then
24644        ˇ                continue
24645        ˇ            elif [ \"$value\" -lt 0 ]; then
24646        ˇ                break
24647        ˇ            else
24648        ˇ                echo \"$item\"
24649        ˇ            fi
24650        ˇ        done
24651        ˇ    done
24652        ˇ}
24653    "});
24654    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
24655    cx.assert_editor_state(indoc! {"
24656        function main() {
24657        #ˇ    for item in $items; do
24658        #ˇ        while [ -n \"$item\" ]; do
24659        #ˇ            if [ \"$value\" -gt 10 ]; then
24660        #ˇ                continue
24661        #ˇ            elif [ \"$value\" -lt 0 ]; then
24662        #ˇ                break
24663        #ˇ            else
24664        #ˇ                echo \"$item\"
24665        #ˇ            fi
24666        #ˇ        done
24667        #ˇ    done
24668        #ˇ}
24669    "});
24670}
24671
24672#[gpui::test]
24673async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
24674    init_test(cx, |_| {});
24675
24676    let mut cx = EditorTestContext::new(cx).await;
24677    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24678    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24679
24680    // test `else` auto outdents when typed inside `if` block
24681    cx.set_state(indoc! {"
24682        if [ \"$1\" = \"test\" ]; then
24683            echo \"foo bar\"
24684            ˇ
24685    "});
24686    cx.update_editor(|editor, window, cx| {
24687        editor.handle_input("else", window, cx);
24688    });
24689    cx.assert_editor_state(indoc! {"
24690        if [ \"$1\" = \"test\" ]; then
24691            echo \"foo bar\"
24692        elseˇ
24693    "});
24694
24695    // test `elif` auto outdents when typed inside `if` block
24696    cx.set_state(indoc! {"
24697        if [ \"$1\" = \"test\" ]; then
24698            echo \"foo bar\"
24699            ˇ
24700    "});
24701    cx.update_editor(|editor, window, cx| {
24702        editor.handle_input("elif", window, cx);
24703    });
24704    cx.assert_editor_state(indoc! {"
24705        if [ \"$1\" = \"test\" ]; then
24706            echo \"foo bar\"
24707        elifˇ
24708    "});
24709
24710    // test `fi` auto outdents when typed inside `else` block
24711    cx.set_state(indoc! {"
24712        if [ \"$1\" = \"test\" ]; then
24713            echo \"foo bar\"
24714        else
24715            echo \"bar baz\"
24716            ˇ
24717    "});
24718    cx.update_editor(|editor, window, cx| {
24719        editor.handle_input("fi", window, cx);
24720    });
24721    cx.assert_editor_state(indoc! {"
24722        if [ \"$1\" = \"test\" ]; then
24723            echo \"foo bar\"
24724        else
24725            echo \"bar baz\"
24726        fiˇ
24727    "});
24728
24729    // test `done` auto outdents when typed inside `while` block
24730    cx.set_state(indoc! {"
24731        while read line; do
24732            echo \"$line\"
24733            ˇ
24734    "});
24735    cx.update_editor(|editor, window, cx| {
24736        editor.handle_input("done", window, cx);
24737    });
24738    cx.assert_editor_state(indoc! {"
24739        while read line; do
24740            echo \"$line\"
24741        doneˇ
24742    "});
24743
24744    // test `done` auto outdents when typed inside `for` block
24745    cx.set_state(indoc! {"
24746        for file in *.txt; do
24747            cat \"$file\"
24748            ˇ
24749    "});
24750    cx.update_editor(|editor, window, cx| {
24751        editor.handle_input("done", window, cx);
24752    });
24753    cx.assert_editor_state(indoc! {"
24754        for file in *.txt; do
24755            cat \"$file\"
24756        doneˇ
24757    "});
24758
24759    // test `esac` auto outdents when typed inside `case` block
24760    cx.set_state(indoc! {"
24761        case \"$1\" in
24762            start)
24763                echo \"foo bar\"
24764                ;;
24765            stop)
24766                echo \"bar baz\"
24767                ;;
24768            ˇ
24769    "});
24770    cx.update_editor(|editor, window, cx| {
24771        editor.handle_input("esac", window, cx);
24772    });
24773    cx.assert_editor_state(indoc! {"
24774        case \"$1\" in
24775            start)
24776                echo \"foo bar\"
24777                ;;
24778            stop)
24779                echo \"bar baz\"
24780                ;;
24781        esacˇ
24782    "});
24783
24784    // test `*)` auto outdents when typed inside `case` block
24785    cx.set_state(indoc! {"
24786        case \"$1\" in
24787            start)
24788                echo \"foo bar\"
24789                ;;
24790                ˇ
24791    "});
24792    cx.update_editor(|editor, window, cx| {
24793        editor.handle_input("*)", window, cx);
24794    });
24795    cx.assert_editor_state(indoc! {"
24796        case \"$1\" in
24797            start)
24798                echo \"foo bar\"
24799                ;;
24800            *)ˇ
24801    "});
24802
24803    // test `fi` outdents to correct level with nested if blocks
24804    cx.set_state(indoc! {"
24805        if [ \"$1\" = \"test\" ]; then
24806            echo \"outer if\"
24807            if [ \"$2\" = \"debug\" ]; then
24808                echo \"inner if\"
24809                ˇ
24810    "});
24811    cx.update_editor(|editor, window, cx| {
24812        editor.handle_input("fi", window, cx);
24813    });
24814    cx.assert_editor_state(indoc! {"
24815        if [ \"$1\" = \"test\" ]; then
24816            echo \"outer if\"
24817            if [ \"$2\" = \"debug\" ]; then
24818                echo \"inner if\"
24819            fiˇ
24820    "});
24821}
24822
24823#[gpui::test]
24824async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
24825    init_test(cx, |_| {});
24826    update_test_language_settings(cx, |settings| {
24827        settings.defaults.extend_comment_on_newline = Some(false);
24828    });
24829    let mut cx = EditorTestContext::new(cx).await;
24830    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24831    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24832
24833    // test correct indent after newline on comment
24834    cx.set_state(indoc! {"
24835        # COMMENT:ˇ
24836    "});
24837    cx.update_editor(|editor, window, cx| {
24838        editor.newline(&Newline, window, cx);
24839    });
24840    cx.assert_editor_state(indoc! {"
24841        # COMMENT:
24842        ˇ
24843    "});
24844
24845    // test correct indent after newline after `then`
24846    cx.set_state(indoc! {"
24847
24848        if [ \"$1\" = \"test\" ]; thenˇ
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
24856        if [ \"$1\" = \"test\" ]; then
24857            ˇ
24858    "});
24859
24860    // test correct indent after newline after `else`
24861    cx.set_state(indoc! {"
24862        if [ \"$1\" = \"test\" ]; then
24863        elseˇ
24864    "});
24865    cx.update_editor(|editor, window, cx| {
24866        editor.newline(&Newline, window, cx);
24867    });
24868    cx.run_until_parked();
24869    cx.assert_editor_state(indoc! {"
24870        if [ \"$1\" = \"test\" ]; then
24871        else
24872            ˇ
24873    "});
24874
24875    // test correct indent after newline after `elif`
24876    cx.set_state(indoc! {"
24877        if [ \"$1\" = \"test\" ]; then
24878        elifˇ
24879    "});
24880    cx.update_editor(|editor, window, cx| {
24881        editor.newline(&Newline, window, cx);
24882    });
24883    cx.run_until_parked();
24884    cx.assert_editor_state(indoc! {"
24885        if [ \"$1\" = \"test\" ]; then
24886        elif
24887            ˇ
24888    "});
24889
24890    // test correct indent after newline after `do`
24891    cx.set_state(indoc! {"
24892        for file in *.txt; doˇ
24893    "});
24894    cx.update_editor(|editor, window, cx| {
24895        editor.newline(&Newline, window, cx);
24896    });
24897    cx.run_until_parked();
24898    cx.assert_editor_state(indoc! {"
24899        for file in *.txt; do
24900            ˇ
24901    "});
24902
24903    // test correct indent after newline after case pattern
24904    cx.set_state(indoc! {"
24905        case \"$1\" in
24906            start)ˇ
24907    "});
24908    cx.update_editor(|editor, window, cx| {
24909        editor.newline(&Newline, window, cx);
24910    });
24911    cx.run_until_parked();
24912    cx.assert_editor_state(indoc! {"
24913        case \"$1\" in
24914            start)
24915                ˇ
24916    "});
24917
24918    // test correct indent after newline after case pattern
24919    cx.set_state(indoc! {"
24920        case \"$1\" in
24921            start)
24922                ;;
24923            *)ˇ
24924    "});
24925    cx.update_editor(|editor, window, cx| {
24926        editor.newline(&Newline, window, cx);
24927    });
24928    cx.run_until_parked();
24929    cx.assert_editor_state(indoc! {"
24930        case \"$1\" in
24931            start)
24932                ;;
24933            *)
24934                ˇ
24935    "});
24936
24937    // test correct indent after newline after function opening brace
24938    cx.set_state(indoc! {"
24939        function test() {ˇ}
24940    "});
24941    cx.update_editor(|editor, window, cx| {
24942        editor.newline(&Newline, window, cx);
24943    });
24944    cx.run_until_parked();
24945    cx.assert_editor_state(indoc! {"
24946        function test() {
24947            ˇ
24948        }
24949    "});
24950
24951    // test no extra indent after semicolon on same line
24952    cx.set_state(indoc! {"
24953        echo \"test\"24954    "});
24955    cx.update_editor(|editor, window, cx| {
24956        editor.newline(&Newline, window, cx);
24957    });
24958    cx.run_until_parked();
24959    cx.assert_editor_state(indoc! {"
24960        echo \"test\";
24961        ˇ
24962    "});
24963}
24964
24965fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
24966    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
24967    point..point
24968}
24969
24970#[track_caller]
24971fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
24972    let (text, ranges) = marked_text_ranges(marked_text, true);
24973    assert_eq!(editor.text(cx), text);
24974    assert_eq!(
24975        editor.selections.ranges(cx),
24976        ranges,
24977        "Assert selections are {}",
24978        marked_text
24979    );
24980}
24981
24982pub fn handle_signature_help_request(
24983    cx: &mut EditorLspTestContext,
24984    mocked_response: lsp::SignatureHelp,
24985) -> impl Future<Output = ()> + use<> {
24986    let mut request =
24987        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
24988            let mocked_response = mocked_response.clone();
24989            async move { Ok(Some(mocked_response)) }
24990        });
24991
24992    async move {
24993        request.next().await;
24994    }
24995}
24996
24997#[track_caller]
24998pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
24999    cx.update_editor(|editor, _, _| {
25000        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
25001            let entries = menu.entries.borrow();
25002            let entries = entries
25003                .iter()
25004                .map(|entry| entry.string.as_str())
25005                .collect::<Vec<_>>();
25006            assert_eq!(entries, expected);
25007        } else {
25008            panic!("Expected completions menu");
25009        }
25010    });
25011}
25012
25013/// Handle completion request passing a marked string specifying where the completion
25014/// should be triggered from using '|' character, what range should be replaced, and what completions
25015/// should be returned using '<' and '>' to delimit the range.
25016///
25017/// Also see `handle_completion_request_with_insert_and_replace`.
25018#[track_caller]
25019pub fn handle_completion_request(
25020    marked_string: &str,
25021    completions: Vec<&'static str>,
25022    is_incomplete: bool,
25023    counter: Arc<AtomicUsize>,
25024    cx: &mut EditorLspTestContext,
25025) -> impl Future<Output = ()> {
25026    let complete_from_marker: TextRangeMarker = '|'.into();
25027    let replace_range_marker: TextRangeMarker = ('<', '>').into();
25028    let (_, mut marked_ranges) = marked_text_ranges_by(
25029        marked_string,
25030        vec![complete_from_marker.clone(), replace_range_marker.clone()],
25031    );
25032
25033    let complete_from_position =
25034        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
25035    let replace_range =
25036        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
25037
25038    let mut request =
25039        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
25040            let completions = completions.clone();
25041            counter.fetch_add(1, atomic::Ordering::Release);
25042            async move {
25043                assert_eq!(params.text_document_position.text_document.uri, url.clone());
25044                assert_eq!(
25045                    params.text_document_position.position,
25046                    complete_from_position
25047                );
25048                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
25049                    is_incomplete,
25050                    item_defaults: None,
25051                    items: completions
25052                        .iter()
25053                        .map(|completion_text| lsp::CompletionItem {
25054                            label: completion_text.to_string(),
25055                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
25056                                range: replace_range,
25057                                new_text: completion_text.to_string(),
25058                            })),
25059                            ..Default::default()
25060                        })
25061                        .collect(),
25062                })))
25063            }
25064        });
25065
25066    async move {
25067        request.next().await;
25068    }
25069}
25070
25071/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
25072/// given instead, which also contains an `insert` range.
25073///
25074/// This function uses markers to define ranges:
25075/// - `|` marks the cursor position
25076/// - `<>` marks the replace range
25077/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
25078pub fn handle_completion_request_with_insert_and_replace(
25079    cx: &mut EditorLspTestContext,
25080    marked_string: &str,
25081    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
25082    counter: Arc<AtomicUsize>,
25083) -> impl Future<Output = ()> {
25084    let complete_from_marker: TextRangeMarker = '|'.into();
25085    let replace_range_marker: TextRangeMarker = ('<', '>').into();
25086    let insert_range_marker: TextRangeMarker = ('{', '}').into();
25087
25088    let (_, mut marked_ranges) = marked_text_ranges_by(
25089        marked_string,
25090        vec![
25091            complete_from_marker.clone(),
25092            replace_range_marker.clone(),
25093            insert_range_marker.clone(),
25094        ],
25095    );
25096
25097    let complete_from_position =
25098        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
25099    let replace_range =
25100        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
25101
25102    let insert_range = match marked_ranges.remove(&insert_range_marker) {
25103        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
25104        _ => lsp::Range {
25105            start: replace_range.start,
25106            end: complete_from_position,
25107        },
25108    };
25109
25110    let mut request =
25111        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
25112            let completions = completions.clone();
25113            counter.fetch_add(1, atomic::Ordering::Release);
25114            async move {
25115                assert_eq!(params.text_document_position.text_document.uri, url.clone());
25116                assert_eq!(
25117                    params.text_document_position.position, complete_from_position,
25118                    "marker `|` position doesn't match",
25119                );
25120                Ok(Some(lsp::CompletionResponse::Array(
25121                    completions
25122                        .iter()
25123                        .map(|(label, new_text)| lsp::CompletionItem {
25124                            label: label.to_string(),
25125                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
25126                                lsp::InsertReplaceEdit {
25127                                    insert: insert_range,
25128                                    replace: replace_range,
25129                                    new_text: new_text.to_string(),
25130                                },
25131                            )),
25132                            ..Default::default()
25133                        })
25134                        .collect(),
25135                )))
25136            }
25137        });
25138
25139    async move {
25140        request.next().await;
25141    }
25142}
25143
25144fn handle_resolve_completion_request(
25145    cx: &mut EditorLspTestContext,
25146    edits: Option<Vec<(&'static str, &'static str)>>,
25147) -> impl Future<Output = ()> {
25148    let edits = edits.map(|edits| {
25149        edits
25150            .iter()
25151            .map(|(marked_string, new_text)| {
25152                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
25153                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
25154                lsp::TextEdit::new(replace_range, new_text.to_string())
25155            })
25156            .collect::<Vec<_>>()
25157    });
25158
25159    let mut request =
25160        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
25161            let edits = edits.clone();
25162            async move {
25163                Ok(lsp::CompletionItem {
25164                    additional_text_edits: edits,
25165                    ..Default::default()
25166                })
25167            }
25168        });
25169
25170    async move {
25171        request.next().await;
25172    }
25173}
25174
25175pub(crate) fn update_test_language_settings(
25176    cx: &mut TestAppContext,
25177    f: impl Fn(&mut AllLanguageSettingsContent),
25178) {
25179    cx.update(|cx| {
25180        SettingsStore::update_global(cx, |store, cx| {
25181            store.update_user_settings::<AllLanguageSettings>(cx, f);
25182        });
25183    });
25184}
25185
25186pub(crate) fn update_test_project_settings(
25187    cx: &mut TestAppContext,
25188    f: impl Fn(&mut ProjectSettings),
25189) {
25190    cx.update(|cx| {
25191        SettingsStore::update_global(cx, |store, cx| {
25192            store.update_user_settings::<ProjectSettings>(cx, f);
25193        });
25194    });
25195}
25196
25197pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
25198    cx.update(|cx| {
25199        assets::Assets.load_test_fonts(cx);
25200        let store = SettingsStore::test(cx);
25201        cx.set_global(store);
25202        theme::init(theme::LoadThemes::JustBase, cx);
25203        release_channel::init(SemanticVersion::default(), cx);
25204        client::init_settings(cx);
25205        language::init(cx);
25206        Project::init_settings(cx);
25207        workspace::init_settings(cx);
25208        crate::init(cx);
25209    });
25210    zlog::init_test();
25211    update_test_language_settings(cx, f);
25212}
25213
25214#[track_caller]
25215fn assert_hunk_revert(
25216    not_reverted_text_with_selections: &str,
25217    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
25218    expected_reverted_text_with_selections: &str,
25219    base_text: &str,
25220    cx: &mut EditorLspTestContext,
25221) {
25222    cx.set_state(not_reverted_text_with_selections);
25223    cx.set_head_text(base_text);
25224    cx.executor().run_until_parked();
25225
25226    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
25227        let snapshot = editor.snapshot(window, cx);
25228        let reverted_hunk_statuses = snapshot
25229            .buffer_snapshot
25230            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
25231            .map(|hunk| hunk.status().kind)
25232            .collect::<Vec<_>>();
25233
25234        editor.git_restore(&Default::default(), window, cx);
25235        reverted_hunk_statuses
25236    });
25237    cx.executor().run_until_parked();
25238    cx.assert_editor_state(expected_reverted_text_with_selections);
25239    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
25240}
25241
25242#[gpui::test(iterations = 10)]
25243async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
25244    init_test(cx, |_| {});
25245
25246    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
25247    let counter = diagnostic_requests.clone();
25248
25249    let fs = FakeFs::new(cx.executor());
25250    fs.insert_tree(
25251        path!("/a"),
25252        json!({
25253            "first.rs": "fn main() { let a = 5; }",
25254            "second.rs": "// Test file",
25255        }),
25256    )
25257    .await;
25258
25259    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25260    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25261    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25262
25263    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25264    language_registry.add(rust_lang());
25265    let mut fake_servers = language_registry.register_fake_lsp(
25266        "Rust",
25267        FakeLspAdapter {
25268            capabilities: lsp::ServerCapabilities {
25269                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
25270                    lsp::DiagnosticOptions {
25271                        identifier: None,
25272                        inter_file_dependencies: true,
25273                        workspace_diagnostics: true,
25274                        work_done_progress_options: Default::default(),
25275                    },
25276                )),
25277                ..Default::default()
25278            },
25279            ..Default::default()
25280        },
25281    );
25282
25283    let editor = workspace
25284        .update(cx, |workspace, window, cx| {
25285            workspace.open_abs_path(
25286                PathBuf::from(path!("/a/first.rs")),
25287                OpenOptions::default(),
25288                window,
25289                cx,
25290            )
25291        })
25292        .unwrap()
25293        .await
25294        .unwrap()
25295        .downcast::<Editor>()
25296        .unwrap();
25297    let fake_server = fake_servers.next().await.unwrap();
25298    let server_id = fake_server.server.server_id();
25299    let mut first_request = fake_server
25300        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
25301            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
25302            let result_id = Some(new_result_id.to_string());
25303            assert_eq!(
25304                params.text_document.uri,
25305                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25306            );
25307            async move {
25308                Ok(lsp::DocumentDiagnosticReportResult::Report(
25309                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
25310                        related_documents: None,
25311                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
25312                            items: Vec::new(),
25313                            result_id,
25314                        },
25315                    }),
25316                ))
25317            }
25318        });
25319
25320    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
25321        project.update(cx, |project, cx| {
25322            let buffer_id = editor
25323                .read(cx)
25324                .buffer()
25325                .read(cx)
25326                .as_singleton()
25327                .expect("created a singleton buffer")
25328                .read(cx)
25329                .remote_id();
25330            let buffer_result_id = project
25331                .lsp_store()
25332                .read(cx)
25333                .result_id(server_id, buffer_id, cx);
25334            assert_eq!(expected, buffer_result_id);
25335        });
25336    };
25337
25338    ensure_result_id(None, cx);
25339    cx.executor().advance_clock(Duration::from_millis(60));
25340    cx.executor().run_until_parked();
25341    assert_eq!(
25342        diagnostic_requests.load(atomic::Ordering::Acquire),
25343        1,
25344        "Opening file should trigger diagnostic request"
25345    );
25346    first_request
25347        .next()
25348        .await
25349        .expect("should have sent the first diagnostics pull request");
25350    ensure_result_id(Some("1".to_string()), cx);
25351
25352    // Editing should trigger diagnostics
25353    editor.update_in(cx, |editor, window, cx| {
25354        editor.handle_input("2", window, cx)
25355    });
25356    cx.executor().advance_clock(Duration::from_millis(60));
25357    cx.executor().run_until_parked();
25358    assert_eq!(
25359        diagnostic_requests.load(atomic::Ordering::Acquire),
25360        2,
25361        "Editing should trigger diagnostic request"
25362    );
25363    ensure_result_id(Some("2".to_string()), cx);
25364
25365    // Moving cursor should not trigger diagnostic request
25366    editor.update_in(cx, |editor, window, cx| {
25367        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25368            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
25369        });
25370    });
25371    cx.executor().advance_clock(Duration::from_millis(60));
25372    cx.executor().run_until_parked();
25373    assert_eq!(
25374        diagnostic_requests.load(atomic::Ordering::Acquire),
25375        2,
25376        "Cursor movement should not trigger diagnostic request"
25377    );
25378    ensure_result_id(Some("2".to_string()), cx);
25379    // Multiple rapid edits should be debounced
25380    for _ in 0..5 {
25381        editor.update_in(cx, |editor, window, cx| {
25382            editor.handle_input("x", window, cx)
25383        });
25384    }
25385    cx.executor().advance_clock(Duration::from_millis(60));
25386    cx.executor().run_until_parked();
25387
25388    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
25389    assert!(
25390        final_requests <= 4,
25391        "Multiple rapid edits should be debounced (got {final_requests} requests)",
25392    );
25393    ensure_result_id(Some(final_requests.to_string()), cx);
25394}
25395
25396#[gpui::test]
25397async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
25398    // Regression test for issue #11671
25399    // Previously, adding a cursor after moving multiple cursors would reset
25400    // the cursor count instead of adding to the existing cursors.
25401    init_test(cx, |_| {});
25402    let mut cx = EditorTestContext::new(cx).await;
25403
25404    // Create a simple buffer with cursor at start
25405    cx.set_state(indoc! {"
25406        ˇaaaa
25407        bbbb
25408        cccc
25409        dddd
25410        eeee
25411        ffff
25412        gggg
25413        hhhh"});
25414
25415    // Add 2 cursors below (so we have 3 total)
25416    cx.update_editor(|editor, window, cx| {
25417        editor.add_selection_below(&Default::default(), window, cx);
25418        editor.add_selection_below(&Default::default(), window, cx);
25419    });
25420
25421    // Verify we have 3 cursors
25422    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
25423    assert_eq!(
25424        initial_count, 3,
25425        "Should have 3 cursors after adding 2 below"
25426    );
25427
25428    // Move down one line
25429    cx.update_editor(|editor, window, cx| {
25430        editor.move_down(&MoveDown, window, cx);
25431    });
25432
25433    // Add another cursor below
25434    cx.update_editor(|editor, window, cx| {
25435        editor.add_selection_below(&Default::default(), window, cx);
25436    });
25437
25438    // Should now have 4 cursors (3 original + 1 new)
25439    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
25440    assert_eq!(
25441        final_count, 4,
25442        "Should have 4 cursors after moving and adding another"
25443    );
25444}
25445
25446#[gpui::test(iterations = 10)]
25447async fn test_document_colors(cx: &mut TestAppContext) {
25448    let expected_color = Rgba {
25449        r: 0.33,
25450        g: 0.33,
25451        b: 0.33,
25452        a: 0.33,
25453    };
25454
25455    init_test(cx, |_| {});
25456
25457    let fs = FakeFs::new(cx.executor());
25458    fs.insert_tree(
25459        path!("/a"),
25460        json!({
25461            "first.rs": "fn main() { let a = 5; }",
25462        }),
25463    )
25464    .await;
25465
25466    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25467    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25468    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25469
25470    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25471    language_registry.add(rust_lang());
25472    let mut fake_servers = language_registry.register_fake_lsp(
25473        "Rust",
25474        FakeLspAdapter {
25475            capabilities: lsp::ServerCapabilities {
25476                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
25477                ..lsp::ServerCapabilities::default()
25478            },
25479            name: "rust-analyzer",
25480            ..FakeLspAdapter::default()
25481        },
25482    );
25483    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
25484        "Rust",
25485        FakeLspAdapter {
25486            capabilities: lsp::ServerCapabilities {
25487                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
25488                ..lsp::ServerCapabilities::default()
25489            },
25490            name: "not-rust-analyzer",
25491            ..FakeLspAdapter::default()
25492        },
25493    );
25494
25495    let editor = workspace
25496        .update(cx, |workspace, window, cx| {
25497            workspace.open_abs_path(
25498                PathBuf::from(path!("/a/first.rs")),
25499                OpenOptions::default(),
25500                window,
25501                cx,
25502            )
25503        })
25504        .unwrap()
25505        .await
25506        .unwrap()
25507        .downcast::<Editor>()
25508        .unwrap();
25509    let fake_language_server = fake_servers.next().await.unwrap();
25510    let fake_language_server_without_capabilities =
25511        fake_servers_without_capabilities.next().await.unwrap();
25512    let requests_made = Arc::new(AtomicUsize::new(0));
25513    let closure_requests_made = Arc::clone(&requests_made);
25514    let mut color_request_handle = fake_language_server
25515        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
25516            let requests_made = Arc::clone(&closure_requests_made);
25517            async move {
25518                assert_eq!(
25519                    params.text_document.uri,
25520                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25521                );
25522                requests_made.fetch_add(1, atomic::Ordering::Release);
25523                Ok(vec![
25524                    lsp::ColorInformation {
25525                        range: lsp::Range {
25526                            start: lsp::Position {
25527                                line: 0,
25528                                character: 0,
25529                            },
25530                            end: lsp::Position {
25531                                line: 0,
25532                                character: 1,
25533                            },
25534                        },
25535                        color: lsp::Color {
25536                            red: 0.33,
25537                            green: 0.33,
25538                            blue: 0.33,
25539                            alpha: 0.33,
25540                        },
25541                    },
25542                    lsp::ColorInformation {
25543                        range: lsp::Range {
25544                            start: lsp::Position {
25545                                line: 0,
25546                                character: 0,
25547                            },
25548                            end: lsp::Position {
25549                                line: 0,
25550                                character: 1,
25551                            },
25552                        },
25553                        color: lsp::Color {
25554                            red: 0.33,
25555                            green: 0.33,
25556                            blue: 0.33,
25557                            alpha: 0.33,
25558                        },
25559                    },
25560                ])
25561            }
25562        });
25563
25564    let _handle = fake_language_server_without_capabilities
25565        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
25566            panic!("Should not be called");
25567        });
25568    cx.executor().advance_clock(Duration::from_millis(100));
25569    color_request_handle.next().await.unwrap();
25570    cx.run_until_parked();
25571    assert_eq!(
25572        1,
25573        requests_made.load(atomic::Ordering::Acquire),
25574        "Should query for colors once per editor open"
25575    );
25576    editor.update_in(cx, |editor, _, cx| {
25577        assert_eq!(
25578            vec![expected_color],
25579            extract_color_inlays(editor, cx),
25580            "Should have an initial inlay"
25581        );
25582    });
25583
25584    // opening another file in a split should not influence the LSP query counter
25585    workspace
25586        .update(cx, |workspace, window, cx| {
25587            assert_eq!(
25588                workspace.panes().len(),
25589                1,
25590                "Should have one pane with one editor"
25591            );
25592            workspace.move_item_to_pane_in_direction(
25593                &MoveItemToPaneInDirection {
25594                    direction: SplitDirection::Right,
25595                    focus: false,
25596                    clone: true,
25597                },
25598                window,
25599                cx,
25600            );
25601        })
25602        .unwrap();
25603    cx.run_until_parked();
25604    workspace
25605        .update(cx, |workspace, _, cx| {
25606            let panes = workspace.panes();
25607            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
25608            for pane in panes {
25609                let editor = pane
25610                    .read(cx)
25611                    .active_item()
25612                    .and_then(|item| item.downcast::<Editor>())
25613                    .expect("Should have opened an editor in each split");
25614                let editor_file = editor
25615                    .read(cx)
25616                    .buffer()
25617                    .read(cx)
25618                    .as_singleton()
25619                    .expect("test deals with singleton buffers")
25620                    .read(cx)
25621                    .file()
25622                    .expect("test buffese should have a file")
25623                    .path();
25624                assert_eq!(
25625                    editor_file.as_ref(),
25626                    Path::new("first.rs"),
25627                    "Both editors should be opened for the same file"
25628                )
25629            }
25630        })
25631        .unwrap();
25632
25633    cx.executor().advance_clock(Duration::from_millis(500));
25634    let save = editor.update_in(cx, |editor, window, cx| {
25635        editor.move_to_end(&MoveToEnd, window, cx);
25636        editor.handle_input("dirty", window, cx);
25637        editor.save(
25638            SaveOptions {
25639                format: true,
25640                autosave: true,
25641            },
25642            project.clone(),
25643            window,
25644            cx,
25645        )
25646    });
25647    save.await.unwrap();
25648
25649    color_request_handle.next().await.unwrap();
25650    cx.run_until_parked();
25651    assert_eq!(
25652        3,
25653        requests_made.load(atomic::Ordering::Acquire),
25654        "Should query for colors once per save and once per formatting after save"
25655    );
25656
25657    drop(editor);
25658    let close = workspace
25659        .update(cx, |workspace, window, cx| {
25660            workspace.active_pane().update(cx, |pane, cx| {
25661                pane.close_active_item(&CloseActiveItem::default(), window, cx)
25662            })
25663        })
25664        .unwrap();
25665    close.await.unwrap();
25666    let close = workspace
25667        .update(cx, |workspace, window, cx| {
25668            workspace.active_pane().update(cx, |pane, cx| {
25669                pane.close_active_item(&CloseActiveItem::default(), window, cx)
25670            })
25671        })
25672        .unwrap();
25673    close.await.unwrap();
25674    assert_eq!(
25675        3,
25676        requests_made.load(atomic::Ordering::Acquire),
25677        "After saving and closing all editors, no extra requests should be made"
25678    );
25679    workspace
25680        .update(cx, |workspace, _, cx| {
25681            assert!(
25682                workspace.active_item(cx).is_none(),
25683                "Should close all editors"
25684            )
25685        })
25686        .unwrap();
25687
25688    workspace
25689        .update(cx, |workspace, window, cx| {
25690            workspace.active_pane().update(cx, |pane, cx| {
25691                pane.navigate_backward(&workspace::GoBack, window, cx);
25692            })
25693        })
25694        .unwrap();
25695    cx.executor().advance_clock(Duration::from_millis(100));
25696    cx.run_until_parked();
25697    let editor = workspace
25698        .update(cx, |workspace, _, cx| {
25699            workspace
25700                .active_item(cx)
25701                .expect("Should have reopened the editor again after navigating back")
25702                .downcast::<Editor>()
25703                .expect("Should be an editor")
25704        })
25705        .unwrap();
25706    color_request_handle.next().await.unwrap();
25707    assert_eq!(
25708        3,
25709        requests_made.load(atomic::Ordering::Acquire),
25710        "Cache should be reused on buffer close and reopen"
25711    );
25712    editor.update(cx, |editor, cx| {
25713        assert_eq!(
25714            vec![expected_color],
25715            extract_color_inlays(editor, cx),
25716            "Should have an initial inlay"
25717        );
25718    });
25719
25720    drop(color_request_handle);
25721    let closure_requests_made = Arc::clone(&requests_made);
25722    let mut empty_color_request_handle = fake_language_server
25723        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
25724            let requests_made = Arc::clone(&closure_requests_made);
25725            async move {
25726                assert_eq!(
25727                    params.text_document.uri,
25728                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25729                );
25730                requests_made.fetch_add(1, atomic::Ordering::Release);
25731                Ok(Vec::new())
25732            }
25733        });
25734    let save = editor.update_in(cx, |editor, window, cx| {
25735        editor.move_to_end(&MoveToEnd, window, cx);
25736        editor.handle_input("dirty_again", window, cx);
25737        editor.save(
25738            SaveOptions {
25739                format: false,
25740                autosave: true,
25741            },
25742            project.clone(),
25743            window,
25744            cx,
25745        )
25746    });
25747    save.await.unwrap();
25748
25749    empty_color_request_handle.next().await.unwrap();
25750    cx.run_until_parked();
25751    assert_eq!(
25752        4,
25753        requests_made.load(atomic::Ordering::Acquire),
25754        "Should query for colors once per save only, as formatting was not requested"
25755    );
25756    editor.update(cx, |editor, cx| {
25757        assert_eq!(
25758            Vec::<Rgba>::new(),
25759            extract_color_inlays(editor, cx),
25760            "Should clear all colors when the server returns an empty response"
25761        );
25762    });
25763}
25764
25765#[gpui::test]
25766async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
25767    init_test(cx, |_| {});
25768    let (editor, cx) = cx.add_window_view(Editor::single_line);
25769    editor.update_in(cx, |editor, window, cx| {
25770        editor.set_text("oops\n\nwow\n", window, cx)
25771    });
25772    cx.run_until_parked();
25773    editor.update(cx, |editor, cx| {
25774        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
25775    });
25776    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
25777    cx.run_until_parked();
25778    editor.update(cx, |editor, cx| {
25779        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
25780    });
25781}
25782
25783#[gpui::test]
25784async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
25785    init_test(cx, |_| {});
25786
25787    cx.update(|cx| {
25788        register_project_item::<Editor>(cx);
25789    });
25790
25791    let fs = FakeFs::new(cx.executor());
25792    fs.insert_tree("/root1", json!({})).await;
25793    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
25794        .await;
25795
25796    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
25797    let (workspace, cx) =
25798        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
25799
25800    let worktree_id = project.update(cx, |project, cx| {
25801        project.worktrees(cx).next().unwrap().read(cx).id()
25802    });
25803
25804    let handle = workspace
25805        .update_in(cx, |workspace, window, cx| {
25806            let project_path = (worktree_id, "one.pdf");
25807            workspace.open_path(project_path, None, true, window, cx)
25808        })
25809        .await
25810        .unwrap();
25811
25812    assert_eq!(
25813        handle.to_any().entity_type(),
25814        TypeId::of::<InvalidBufferView>()
25815    );
25816}
25817
25818#[gpui::test]
25819async fn test_select_next_prev_syntax_node(cx: &mut TestAppContext) {
25820    init_test(cx, |_| {});
25821
25822    let language = Arc::new(Language::new(
25823        LanguageConfig::default(),
25824        Some(tree_sitter_rust::LANGUAGE.into()),
25825    ));
25826
25827    // Test hierarchical sibling navigation
25828    let text = r#"
25829        fn outer() {
25830            if condition {
25831                let a = 1;
25832            }
25833            let b = 2;
25834        }
25835
25836        fn another() {
25837            let c = 3;
25838        }
25839    "#;
25840
25841    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
25842    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
25843    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
25844
25845    // Wait for parsing to complete
25846    editor
25847        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
25848        .await;
25849
25850    editor.update_in(cx, |editor, window, cx| {
25851        // Start by selecting "let a = 1;" inside the if block
25852        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25853            s.select_display_ranges([
25854                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 26)
25855            ]);
25856        });
25857
25858        let initial_selection = editor.selections.display_ranges(cx);
25859        assert_eq!(initial_selection.len(), 1, "Should have one selection");
25860
25861        // Test select next sibling - should move up levels to find the next sibling
25862        // Since "let a = 1;" has no siblings in the if block, it should move up
25863        // to find "let b = 2;" which is a sibling of the if block
25864        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
25865        let next_selection = editor.selections.display_ranges(cx);
25866
25867        // Should have a selection and it should be different from the initial
25868        assert_eq!(
25869            next_selection.len(),
25870            1,
25871            "Should have one selection after next"
25872        );
25873        assert_ne!(
25874            next_selection[0], initial_selection[0],
25875            "Next sibling selection should be different"
25876        );
25877
25878        // Test hierarchical navigation by going to the end of the current function
25879        // and trying to navigate to the next function
25880        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25881            s.select_display_ranges([
25882                DisplayPoint::new(DisplayRow(5), 12)..DisplayPoint::new(DisplayRow(5), 22)
25883            ]);
25884        });
25885
25886        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
25887        let function_next_selection = editor.selections.display_ranges(cx);
25888
25889        // Should move to the next function
25890        assert_eq!(
25891            function_next_selection.len(),
25892            1,
25893            "Should have one selection after function next"
25894        );
25895
25896        // Test select previous sibling navigation
25897        editor.select_prev_syntax_node(&SelectPreviousSyntaxNode, window, cx);
25898        let prev_selection = editor.selections.display_ranges(cx);
25899
25900        // Should have a selection and it should be different
25901        assert_eq!(
25902            prev_selection.len(),
25903            1,
25904            "Should have one selection after prev"
25905        );
25906        assert_ne!(
25907            prev_selection[0], function_next_selection[0],
25908            "Previous sibling selection should be different from next"
25909        );
25910    });
25911}
25912
25913#[gpui::test]
25914async fn test_next_prev_document_highlight(cx: &mut TestAppContext) {
25915    init_test(cx, |_| {});
25916
25917    let mut cx = EditorTestContext::new(cx).await;
25918    cx.set_state(
25919        "let ˇvariable = 42;
25920let another = variable + 1;
25921let result = variable * 2;",
25922    );
25923
25924    // Set up document highlights manually (simulating LSP response)
25925    cx.update_editor(|editor, _window, cx| {
25926        let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
25927
25928        // Create highlights for "variable" occurrences
25929        let highlight_ranges = [
25930            Point::new(0, 4)..Point::new(0, 12),  // First "variable"
25931            Point::new(1, 14)..Point::new(1, 22), // Second "variable"
25932            Point::new(2, 13)..Point::new(2, 21), // Third "variable"
25933        ];
25934
25935        let anchor_ranges: Vec<_> = highlight_ranges
25936            .iter()
25937            .map(|range| range.clone().to_anchors(&buffer_snapshot))
25938            .collect();
25939
25940        editor.highlight_background::<DocumentHighlightRead>(
25941            &anchor_ranges,
25942            |theme| theme.colors().editor_document_highlight_read_background,
25943            cx,
25944        );
25945    });
25946
25947    // Go to next highlight - should move to second "variable"
25948    cx.update_editor(|editor, window, cx| {
25949        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
25950    });
25951    cx.assert_editor_state(
25952        "let variable = 42;
25953let another = ˇvariable + 1;
25954let result = variable * 2;",
25955    );
25956
25957    // Go to next highlight - should move to third "variable"
25958    cx.update_editor(|editor, window, cx| {
25959        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
25960    });
25961    cx.assert_editor_state(
25962        "let variable = 42;
25963let another = variable + 1;
25964let result = ˇvariable * 2;",
25965    );
25966
25967    // Go to next highlight - should stay at third "variable" (no wrap-around)
25968    cx.update_editor(|editor, window, cx| {
25969        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
25970    });
25971    cx.assert_editor_state(
25972        "let variable = 42;
25973let another = variable + 1;
25974let result = ˇvariable * 2;",
25975    );
25976
25977    // Now test going backwards from third position
25978    cx.update_editor(|editor, window, cx| {
25979        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
25980    });
25981    cx.assert_editor_state(
25982        "let variable = 42;
25983let another = ˇvariable + 1;
25984let result = variable * 2;",
25985    );
25986
25987    // Go to previous highlight - should move to first "variable"
25988    cx.update_editor(|editor, window, cx| {
25989        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
25990    });
25991    cx.assert_editor_state(
25992        "let ˇvariable = 42;
25993let another = variable + 1;
25994let result = variable * 2;",
25995    );
25996
25997    // Go to previous highlight - should stay on first "variable"
25998    cx.update_editor(|editor, window, cx| {
25999        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26000    });
26001    cx.assert_editor_state(
26002        "let ˇvariable = 42;
26003let another = variable + 1;
26004let result = variable * 2;",
26005    );
26006}
26007
26008#[track_caller]
26009fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
26010    editor
26011        .all_inlays(cx)
26012        .into_iter()
26013        .filter_map(|inlay| inlay.get_color())
26014        .map(Rgba::from)
26015        .collect()
26016}