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 collections::HashMap;
   17use futures::{StreamExt, channel::oneshot};
   18use gpui::{
   19    BackgroundExecutor, DismissEvent, Rgba, SemanticVersion, TestAppContext, UpdateGlobal,
   20    VisualTestContext, WindowBounds, WindowOptions, div,
   21};
   22use indoc::indoc;
   23use language::{
   24    BracketPairConfig,
   25    Capability::ReadWrite,
   26    DiagnosticSourceKind, FakeLspAdapter, IndentGuideSettings, LanguageConfig,
   27    LanguageConfigOverride, LanguageMatcher, LanguageName, Override, Point,
   28    language_settings::{
   29        CompletionSettingsContent, FormatterList, LanguageSettingsContent, LspInsertMode,
   30        SelectedFormatter,
   31    },
   32    tree_sitter_python,
   33};
   34use language_settings::Formatter;
   35use lsp::CompletionParams;
   36use multi_buffer::{IndentGuide, PathKey};
   37use parking_lot::Mutex;
   38use pretty_assertions::{assert_eq, assert_ne};
   39use project::{
   40    FakeFs,
   41    debugger::breakpoint_store::{BreakpointState, SourceBreakpoint},
   42    project_settings::LspSettings,
   43};
   44use serde_json::{self, json};
   45use settings::{
   46    AllLanguageSettingsContent, IndentGuideBackgroundColoring, IndentGuideColoring,
   47    ProjectSettingsContent,
   48};
   49use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant};
   50use std::{
   51    iter,
   52    sync::atomic::{self, AtomicUsize},
   53};
   54use test::{build_editor_with_project, editor_lsp_test_context::rust_lang};
   55use text::ToPoint as _;
   56use unindent::Unindent;
   57use util::{
   58    assert_set_eq, path,
   59    rel_path::rel_path,
   60    test::{TextRangeMarker, marked_text_ranges, marked_text_ranges_by, sample_text},
   61    uri,
   62};
   63use workspace::{
   64    CloseActiveItem, CloseAllItems, CloseOtherItems, MoveItemToPaneInDirection, NavigationEntry,
   65    OpenOptions, ViewId,
   66    invalid_buffer_view::InvalidBufferView,
   67    item::{FollowEvent, FollowableItem, Item, ItemHandle, SaveOptions},
   68    register_project_item,
   69};
   70
   71#[gpui::test]
   72fn test_edit_events(cx: &mut TestAppContext) {
   73    init_test(cx, |_| {});
   74
   75    let buffer = cx.new(|cx| {
   76        let mut buffer = language::Buffer::local("123456", cx);
   77        buffer.set_group_interval(Duration::from_secs(1));
   78        buffer
   79    });
   80
   81    let events = Rc::new(RefCell::new(Vec::new()));
   82    let editor1 = cx.add_window({
   83        let events = events.clone();
   84        |window, cx| {
   85            let entity = cx.entity();
   86            cx.subscribe_in(
   87                &entity,
   88                window,
   89                move |_, _, event: &EditorEvent, _, _| match event {
   90                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
   91                    EditorEvent::BufferEdited => {
   92                        events.borrow_mut().push(("editor1", "buffer edited"))
   93                    }
   94                    _ => {}
   95                },
   96            )
   97            .detach();
   98            Editor::for_buffer(buffer.clone(), None, window, cx)
   99        }
  100    });
  101
  102    let editor2 = cx.add_window({
  103        let events = events.clone();
  104        |window, cx| {
  105            cx.subscribe_in(
  106                &cx.entity(),
  107                window,
  108                move |_, _, event: &EditorEvent, _, _| match event {
  109                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
  110                    EditorEvent::BufferEdited => {
  111                        events.borrow_mut().push(("editor2", "buffer edited"))
  112                    }
  113                    _ => {}
  114                },
  115            )
  116            .detach();
  117            Editor::for_buffer(buffer.clone(), None, window, cx)
  118        }
  119    });
  120
  121    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  122
  123    // Mutating editor 1 will emit an `Edited` event only for that editor.
  124    _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
  125    assert_eq!(
  126        mem::take(&mut *events.borrow_mut()),
  127        [
  128            ("editor1", "edited"),
  129            ("editor1", "buffer edited"),
  130            ("editor2", "buffer edited"),
  131        ]
  132    );
  133
  134    // Mutating editor 2 will emit an `Edited` event only for that editor.
  135    _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
  136    assert_eq!(
  137        mem::take(&mut *events.borrow_mut()),
  138        [
  139            ("editor2", "edited"),
  140            ("editor1", "buffer edited"),
  141            ("editor2", "buffer edited"),
  142        ]
  143    );
  144
  145    // Undoing on editor 1 will emit an `Edited` event only for that editor.
  146    _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  147    assert_eq!(
  148        mem::take(&mut *events.borrow_mut()),
  149        [
  150            ("editor1", "edited"),
  151            ("editor1", "buffer edited"),
  152            ("editor2", "buffer edited"),
  153        ]
  154    );
  155
  156    // Redoing on editor 1 will emit an `Edited` event only for that editor.
  157    _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  158    assert_eq!(
  159        mem::take(&mut *events.borrow_mut()),
  160        [
  161            ("editor1", "edited"),
  162            ("editor1", "buffer edited"),
  163            ("editor2", "buffer edited"),
  164        ]
  165    );
  166
  167    // Undoing on editor 2 will emit an `Edited` event only for that editor.
  168    _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  169    assert_eq!(
  170        mem::take(&mut *events.borrow_mut()),
  171        [
  172            ("editor2", "edited"),
  173            ("editor1", "buffer edited"),
  174            ("editor2", "buffer edited"),
  175        ]
  176    );
  177
  178    // Redoing on editor 2 will emit an `Edited` event only for that editor.
  179    _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  180    assert_eq!(
  181        mem::take(&mut *events.borrow_mut()),
  182        [
  183            ("editor2", "edited"),
  184            ("editor1", "buffer edited"),
  185            ("editor2", "buffer edited"),
  186        ]
  187    );
  188
  189    // No event is emitted when the mutation is a no-op.
  190    _ = editor2.update(cx, |editor, window, cx| {
  191        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  192            s.select_ranges([0..0])
  193        });
  194
  195        editor.backspace(&Backspace, window, cx);
  196    });
  197    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  198}
  199
  200#[gpui::test]
  201fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
  202    init_test(cx, |_| {});
  203
  204    let mut now = Instant::now();
  205    let group_interval = Duration::from_millis(1);
  206    let buffer = cx.new(|cx| {
  207        let mut buf = language::Buffer::local("123456", cx);
  208        buf.set_group_interval(group_interval);
  209        buf
  210    });
  211    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  212    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
  213
  214    _ = editor.update(cx, |editor, window, cx| {
  215        editor.start_transaction_at(now, window, cx);
  216        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  217            s.select_ranges([2..4])
  218        });
  219
  220        editor.insert("cd", window, cx);
  221        editor.end_transaction_at(now, cx);
  222        assert_eq!(editor.text(cx), "12cd56");
  223        assert_eq!(editor.selections.ranges(cx), vec![4..4]);
  224
  225        editor.start_transaction_at(now, window, cx);
  226        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  227            s.select_ranges([4..5])
  228        });
  229        editor.insert("e", window, cx);
  230        editor.end_transaction_at(now, cx);
  231        assert_eq!(editor.text(cx), "12cde6");
  232        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  233
  234        now += group_interval + Duration::from_millis(1);
  235        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  236            s.select_ranges([2..2])
  237        });
  238
  239        // Simulate an edit in another editor
  240        buffer.update(cx, |buffer, cx| {
  241            buffer.start_transaction_at(now, cx);
  242            buffer.edit([(0..1, "a")], None, cx);
  243            buffer.edit([(1..1, "b")], None, cx);
  244            buffer.end_transaction_at(now, cx);
  245        });
  246
  247        assert_eq!(editor.text(cx), "ab2cde6");
  248        assert_eq!(editor.selections.ranges(cx), vec![3..3]);
  249
  250        // Last transaction happened past the group interval in a different editor.
  251        // Undo it individually and don't restore selections.
  252        editor.undo(&Undo, window, cx);
  253        assert_eq!(editor.text(cx), "12cde6");
  254        assert_eq!(editor.selections.ranges(cx), vec![2..2]);
  255
  256        // First two transactions happened within the group interval in this editor.
  257        // Undo them together and restore selections.
  258        editor.undo(&Undo, window, cx);
  259        editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
  260        assert_eq!(editor.text(cx), "123456");
  261        assert_eq!(editor.selections.ranges(cx), vec![0..0]);
  262
  263        // Redo the first two transactions together.
  264        editor.redo(&Redo, window, cx);
  265        assert_eq!(editor.text(cx), "12cde6");
  266        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  267
  268        // Redo the last transaction on its own.
  269        editor.redo(&Redo, window, cx);
  270        assert_eq!(editor.text(cx), "ab2cde6");
  271        assert_eq!(editor.selections.ranges(cx), vec![6..6]);
  272
  273        // Test empty transactions.
  274        editor.start_transaction_at(now, window, cx);
  275        editor.end_transaction_at(now, cx);
  276        editor.undo(&Undo, window, cx);
  277        assert_eq!(editor.text(cx), "12cde6");
  278    });
  279}
  280
  281#[gpui::test]
  282fn test_ime_composition(cx: &mut TestAppContext) {
  283    init_test(cx, |_| {});
  284
  285    let buffer = cx.new(|cx| {
  286        let mut buffer = language::Buffer::local("abcde", cx);
  287        // Ensure automatic grouping doesn't occur.
  288        buffer.set_group_interval(Duration::ZERO);
  289        buffer
  290    });
  291
  292    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  293    cx.add_window(|window, cx| {
  294        let mut editor = build_editor(buffer.clone(), window, cx);
  295
  296        // Start a new IME composition.
  297        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  298        editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx);
  299        editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
  300        assert_eq!(editor.text(cx), "äbcde");
  301        assert_eq!(
  302            editor.marked_text_ranges(cx),
  303            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  304        );
  305
  306        // Finalize IME composition.
  307        editor.replace_text_in_range(None, "ā", window, cx);
  308        assert_eq!(editor.text(cx), "ābcde");
  309        assert_eq!(editor.marked_text_ranges(cx), None);
  310
  311        // IME composition edits are grouped and are undone/redone at once.
  312        editor.undo(&Default::default(), window, cx);
  313        assert_eq!(editor.text(cx), "abcde");
  314        assert_eq!(editor.marked_text_ranges(cx), None);
  315        editor.redo(&Default::default(), window, cx);
  316        assert_eq!(editor.text(cx), "ābcde");
  317        assert_eq!(editor.marked_text_ranges(cx), None);
  318
  319        // Start a new IME composition.
  320        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  321        assert_eq!(
  322            editor.marked_text_ranges(cx),
  323            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  324        );
  325
  326        // Undoing during an IME composition cancels it.
  327        editor.undo(&Default::default(), window, cx);
  328        assert_eq!(editor.text(cx), "ābcde");
  329        assert_eq!(editor.marked_text_ranges(cx), None);
  330
  331        // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
  332        editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
  333        assert_eq!(editor.text(cx), "ābcdè");
  334        assert_eq!(
  335            editor.marked_text_ranges(cx),
  336            Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
  337        );
  338
  339        // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
  340        editor.replace_text_in_range(Some(4..999), "ę", window, cx);
  341        assert_eq!(editor.text(cx), "ābcdę");
  342        assert_eq!(editor.marked_text_ranges(cx), None);
  343
  344        // Start a new IME composition with multiple cursors.
  345        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  346            s.select_ranges([
  347                OffsetUtf16(1)..OffsetUtf16(1),
  348                OffsetUtf16(3)..OffsetUtf16(3),
  349                OffsetUtf16(5)..OffsetUtf16(5),
  350            ])
  351        });
  352        editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
  353        assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
  354        assert_eq!(
  355            editor.marked_text_ranges(cx),
  356            Some(vec![
  357                OffsetUtf16(0)..OffsetUtf16(3),
  358                OffsetUtf16(4)..OffsetUtf16(7),
  359                OffsetUtf16(8)..OffsetUtf16(11)
  360            ])
  361        );
  362
  363        // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
  364        editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
  365        assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
  366        assert_eq!(
  367            editor.marked_text_ranges(cx),
  368            Some(vec![
  369                OffsetUtf16(1)..OffsetUtf16(2),
  370                OffsetUtf16(5)..OffsetUtf16(6),
  371                OffsetUtf16(9)..OffsetUtf16(10)
  372            ])
  373        );
  374
  375        // Finalize IME composition with multiple cursors.
  376        editor.replace_text_in_range(Some(9..10), "2", window, cx);
  377        assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
  378        assert_eq!(editor.marked_text_ranges(cx), None);
  379
  380        editor
  381    });
  382}
  383
  384#[gpui::test]
  385fn test_selection_with_mouse(cx: &mut TestAppContext) {
  386    init_test(cx, |_| {});
  387
  388    let editor = cx.add_window(|window, cx| {
  389        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  390        build_editor(buffer, window, cx)
  391    });
  392
  393    _ = editor.update(cx, |editor, window, cx| {
  394        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  395    });
  396    assert_eq!(
  397        editor
  398            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  399            .unwrap(),
  400        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  401    );
  402
  403    _ = editor.update(cx, |editor, window, cx| {
  404        editor.update_selection(
  405            DisplayPoint::new(DisplayRow(3), 3),
  406            0,
  407            gpui::Point::<f32>::default(),
  408            window,
  409            cx,
  410        );
  411    });
  412
  413    assert_eq!(
  414        editor
  415            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  416            .unwrap(),
  417        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  418    );
  419
  420    _ = editor.update(cx, |editor, window, cx| {
  421        editor.update_selection(
  422            DisplayPoint::new(DisplayRow(1), 1),
  423            0,
  424            gpui::Point::<f32>::default(),
  425            window,
  426            cx,
  427        );
  428    });
  429
  430    assert_eq!(
  431        editor
  432            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  433            .unwrap(),
  434        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  435    );
  436
  437    _ = editor.update(cx, |editor, window, cx| {
  438        editor.end_selection(window, cx);
  439        editor.update_selection(
  440            DisplayPoint::new(DisplayRow(3), 3),
  441            0,
  442            gpui::Point::<f32>::default(),
  443            window,
  444            cx,
  445        );
  446    });
  447
  448    assert_eq!(
  449        editor
  450            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  451            .unwrap(),
  452        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  453    );
  454
  455    _ = editor.update(cx, |editor, window, cx| {
  456        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
  457        editor.update_selection(
  458            DisplayPoint::new(DisplayRow(0), 0),
  459            0,
  460            gpui::Point::<f32>::default(),
  461            window,
  462            cx,
  463        );
  464    });
  465
  466    assert_eq!(
  467        editor
  468            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  469            .unwrap(),
  470        [
  471            DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
  472            DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
  473        ]
  474    );
  475
  476    _ = editor.update(cx, |editor, window, cx| {
  477        editor.end_selection(window, cx);
  478    });
  479
  480    assert_eq!(
  481        editor
  482            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  483            .unwrap(),
  484        [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
  485    );
  486}
  487
  488#[gpui::test]
  489fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
  490    init_test(cx, |_| {});
  491
  492    let editor = cx.add_window(|window, cx| {
  493        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  494        build_editor(buffer, window, cx)
  495    });
  496
  497    _ = editor.update(cx, |editor, window, cx| {
  498        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
  499    });
  500
  501    _ = editor.update(cx, |editor, window, cx| {
  502        editor.end_selection(window, cx);
  503    });
  504
  505    _ = editor.update(cx, |editor, window, cx| {
  506        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
  507    });
  508
  509    _ = editor.update(cx, |editor, window, cx| {
  510        editor.end_selection(window, cx);
  511    });
  512
  513    assert_eq!(
  514        editor
  515            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  516            .unwrap(),
  517        [
  518            DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
  519            DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
  520        ]
  521    );
  522
  523    _ = editor.update(cx, |editor, window, cx| {
  524        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
  525    });
  526
  527    _ = editor.update(cx, |editor, window, cx| {
  528        editor.end_selection(window, cx);
  529    });
  530
  531    assert_eq!(
  532        editor
  533            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  534            .unwrap(),
  535        [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  536    );
  537}
  538
  539#[gpui::test]
  540fn test_canceling_pending_selection(cx: &mut TestAppContext) {
  541    init_test(cx, |_| {});
  542
  543    let editor = cx.add_window(|window, cx| {
  544        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  545        build_editor(buffer, window, cx)
  546    });
  547
  548    _ = editor.update(cx, |editor, window, cx| {
  549        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  550        assert_eq!(
  551            editor.selections.display_ranges(cx),
  552            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  553        );
  554    });
  555
  556    _ = editor.update(cx, |editor, window, cx| {
  557        editor.update_selection(
  558            DisplayPoint::new(DisplayRow(3), 3),
  559            0,
  560            gpui::Point::<f32>::default(),
  561            window,
  562            cx,
  563        );
  564        assert_eq!(
  565            editor.selections.display_ranges(cx),
  566            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  567        );
  568    });
  569
  570    _ = editor.update(cx, |editor, window, cx| {
  571        editor.cancel(&Cancel, window, cx);
  572        editor.update_selection(
  573            DisplayPoint::new(DisplayRow(1), 1),
  574            0,
  575            gpui::Point::<f32>::default(),
  576            window,
  577            cx,
  578        );
  579        assert_eq!(
  580            editor.selections.display_ranges(cx),
  581            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  582        );
  583    });
  584}
  585
  586#[gpui::test]
  587fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
  588    init_test(cx, |_| {});
  589
  590    let editor = cx.add_window(|window, cx| {
  591        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  592        build_editor(buffer, window, cx)
  593    });
  594
  595    _ = editor.update(cx, |editor, window, cx| {
  596        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  597        assert_eq!(
  598            editor.selections.display_ranges(cx),
  599            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  600        );
  601
  602        editor.move_down(&Default::default(), window, cx);
  603        assert_eq!(
  604            editor.selections.display_ranges(cx),
  605            [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  606        );
  607
  608        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  609        assert_eq!(
  610            editor.selections.display_ranges(cx),
  611            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  612        );
  613
  614        editor.move_up(&Default::default(), window, cx);
  615        assert_eq!(
  616            editor.selections.display_ranges(cx),
  617            [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
  618        );
  619    });
  620}
  621
  622#[gpui::test]
  623fn test_clone(cx: &mut TestAppContext) {
  624    init_test(cx, |_| {});
  625
  626    let (text, selection_ranges) = marked_text_ranges(
  627        indoc! {"
  628            one
  629            two
  630            threeˇ
  631            four
  632            fiveˇ
  633        "},
  634        true,
  635    );
  636
  637    let editor = cx.add_window(|window, cx| {
  638        let buffer = MultiBuffer::build_simple(&text, cx);
  639        build_editor(buffer, window, cx)
  640    });
  641
  642    _ = editor.update(cx, |editor, window, cx| {
  643        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  644            s.select_ranges(selection_ranges.clone())
  645        });
  646        editor.fold_creases(
  647            vec![
  648                Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
  649                Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
  650            ],
  651            true,
  652            window,
  653            cx,
  654        );
  655    });
  656
  657    let cloned_editor = editor
  658        .update(cx, |editor, _, cx| {
  659            cx.open_window(Default::default(), |window, cx| {
  660                cx.new(|cx| editor.clone(window, cx))
  661            })
  662        })
  663        .unwrap()
  664        .unwrap();
  665
  666    let snapshot = editor
  667        .update(cx, |e, window, cx| e.snapshot(window, cx))
  668        .unwrap();
  669    let cloned_snapshot = cloned_editor
  670        .update(cx, |e, window, cx| e.snapshot(window, cx))
  671        .unwrap();
  672
  673    assert_eq!(
  674        cloned_editor
  675            .update(cx, |e, _, cx| e.display_text(cx))
  676            .unwrap(),
  677        editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
  678    );
  679    assert_eq!(
  680        cloned_snapshot
  681            .folds_in_range(0..text.len())
  682            .collect::<Vec<_>>(),
  683        snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
  684    );
  685    assert_set_eq!(
  686        cloned_editor
  687            .update(cx, |editor, _, cx| editor.selections.ranges::<Point>(cx))
  688            .unwrap(),
  689        editor
  690            .update(cx, |editor, _, cx| editor.selections.ranges(cx))
  691            .unwrap()
  692    );
  693    assert_set_eq!(
  694        cloned_editor
  695            .update(cx, |e, _window, cx| e.selections.display_ranges(cx))
  696            .unwrap(),
  697        editor
  698            .update(cx, |e, _, cx| e.selections.display_ranges(cx))
  699            .unwrap()
  700    );
  701}
  702
  703#[gpui::test]
  704async fn test_navigation_history(cx: &mut TestAppContext) {
  705    init_test(cx, |_| {});
  706
  707    use workspace::item::Item;
  708
  709    let fs = FakeFs::new(cx.executor());
  710    let project = Project::test(fs, [], cx).await;
  711    let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
  712    let pane = workspace
  713        .update(cx, |workspace, _, _| workspace.active_pane().clone())
  714        .unwrap();
  715
  716    _ = workspace.update(cx, |_v, window, cx| {
  717        cx.new(|cx| {
  718            let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
  719            let mut editor = build_editor(buffer, window, cx);
  720            let handle = cx.entity();
  721            editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
  722
  723            fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
  724                editor.nav_history.as_mut().unwrap().pop_backward(cx)
  725            }
  726
  727            // Move the cursor a small distance.
  728            // Nothing is added to the navigation history.
  729            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  730                s.select_display_ranges([
  731                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
  732                ])
  733            });
  734            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  735                s.select_display_ranges([
  736                    DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
  737                ])
  738            });
  739            assert!(pop_history(&mut editor, cx).is_none());
  740
  741            // Move the cursor a large distance.
  742            // The history can jump back to the previous position.
  743            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  744                s.select_display_ranges([
  745                    DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
  746                ])
  747            });
  748            let nav_entry = pop_history(&mut editor, cx).unwrap();
  749            editor.navigate(nav_entry.data.unwrap(), window, cx);
  750            assert_eq!(nav_entry.item.id(), cx.entity_id());
  751            assert_eq!(
  752                editor.selections.display_ranges(cx),
  753                &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
  754            );
  755            assert!(pop_history(&mut editor, cx).is_none());
  756
  757            // Move the cursor a small distance via the mouse.
  758            // Nothing is added to the navigation history.
  759            editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
  760            editor.end_selection(window, cx);
  761            assert_eq!(
  762                editor.selections.display_ranges(cx),
  763                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  764            );
  765            assert!(pop_history(&mut editor, cx).is_none());
  766
  767            // Move the cursor a large distance via the mouse.
  768            // The history can jump back to the previous position.
  769            editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
  770            editor.end_selection(window, cx);
  771            assert_eq!(
  772                editor.selections.display_ranges(cx),
  773                &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
  774            );
  775            let nav_entry = pop_history(&mut editor, cx).unwrap();
  776            editor.navigate(nav_entry.data.unwrap(), window, cx);
  777            assert_eq!(nav_entry.item.id(), cx.entity_id());
  778            assert_eq!(
  779                editor.selections.display_ranges(cx),
  780                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  781            );
  782            assert!(pop_history(&mut editor, cx).is_none());
  783
  784            // Set scroll position to check later
  785            editor.set_scroll_position(gpui::Point::<f64>::new(5.5, 5.5), window, cx);
  786            let original_scroll_position = editor.scroll_manager.anchor();
  787
  788            // Jump to the end of the document and adjust scroll
  789            editor.move_to_end(&MoveToEnd, window, cx);
  790            editor.set_scroll_position(gpui::Point::<f64>::new(-2.5, -0.5), window, cx);
  791            assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
  792
  793            let nav_entry = pop_history(&mut editor, cx).unwrap();
  794            editor.navigate(nav_entry.data.unwrap(), window, cx);
  795            assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
  796
  797            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
  798            let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
  799            invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
  800            let invalid_point = Point::new(9999, 0);
  801            editor.navigate(
  802                Box::new(NavigationData {
  803                    cursor_anchor: invalid_anchor,
  804                    cursor_position: invalid_point,
  805                    scroll_anchor: ScrollAnchor {
  806                        anchor: invalid_anchor,
  807                        offset: Default::default(),
  808                    },
  809                    scroll_top_row: invalid_point.row,
  810                }),
  811                window,
  812                cx,
  813            );
  814            assert_eq!(
  815                editor.selections.display_ranges(cx),
  816                &[editor.max_point(cx)..editor.max_point(cx)]
  817            );
  818            assert_eq!(
  819                editor.scroll_position(cx),
  820                gpui::Point::new(0., editor.max_point(cx).row().as_f64())
  821            );
  822
  823            editor
  824        })
  825    });
  826}
  827
  828#[gpui::test]
  829fn test_cancel(cx: &mut TestAppContext) {
  830    init_test(cx, |_| {});
  831
  832    let editor = cx.add_window(|window, cx| {
  833        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  834        build_editor(buffer, window, cx)
  835    });
  836
  837    _ = editor.update(cx, |editor, window, cx| {
  838        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
  839        editor.update_selection(
  840            DisplayPoint::new(DisplayRow(1), 1),
  841            0,
  842            gpui::Point::<f32>::default(),
  843            window,
  844            cx,
  845        );
  846        editor.end_selection(window, cx);
  847
  848        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
  849        editor.update_selection(
  850            DisplayPoint::new(DisplayRow(0), 3),
  851            0,
  852            gpui::Point::<f32>::default(),
  853            window,
  854            cx,
  855        );
  856        editor.end_selection(window, cx);
  857        assert_eq!(
  858            editor.selections.display_ranges(cx),
  859            [
  860                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
  861                DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
  862            ]
  863        );
  864    });
  865
  866    _ = editor.update(cx, |editor, window, cx| {
  867        editor.cancel(&Cancel, window, cx);
  868        assert_eq!(
  869            editor.selections.display_ranges(cx),
  870            [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
  871        );
  872    });
  873
  874    _ = editor.update(cx, |editor, window, cx| {
  875        editor.cancel(&Cancel, window, cx);
  876        assert_eq!(
  877            editor.selections.display_ranges(cx),
  878            [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
  879        );
  880    });
  881}
  882
  883#[gpui::test]
  884fn test_fold_action(cx: &mut TestAppContext) {
  885    init_test(cx, |_| {});
  886
  887    let editor = cx.add_window(|window, cx| {
  888        let buffer = MultiBuffer::build_simple(
  889            &"
  890                impl Foo {
  891                    // Hello!
  892
  893                    fn a() {
  894                        1
  895                    }
  896
  897                    fn b() {
  898                        2
  899                    }
  900
  901                    fn c() {
  902                        3
  903                    }
  904                }
  905            "
  906            .unindent(),
  907            cx,
  908        );
  909        build_editor(buffer, window, cx)
  910    });
  911
  912    _ = editor.update(cx, |editor, window, cx| {
  913        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  914            s.select_display_ranges([
  915                DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
  916            ]);
  917        });
  918        editor.fold(&Fold, window, cx);
  919        assert_eq!(
  920            editor.display_text(cx),
  921            "
  922                impl Foo {
  923                    // Hello!
  924
  925                    fn a() {
  926                        1
  927                    }
  928
  929                    fn b() {⋯
  930                    }
  931
  932                    fn c() {⋯
  933                    }
  934                }
  935            "
  936            .unindent(),
  937        );
  938
  939        editor.fold(&Fold, window, cx);
  940        assert_eq!(
  941            editor.display_text(cx),
  942            "
  943                impl Foo {⋯
  944                }
  945            "
  946            .unindent(),
  947        );
  948
  949        editor.unfold_lines(&UnfoldLines, window, cx);
  950        assert_eq!(
  951            editor.display_text(cx),
  952            "
  953                impl Foo {
  954                    // Hello!
  955
  956                    fn a() {
  957                        1
  958                    }
  959
  960                    fn b() {⋯
  961                    }
  962
  963                    fn c() {⋯
  964                    }
  965                }
  966            "
  967            .unindent(),
  968        );
  969
  970        editor.unfold_lines(&UnfoldLines, window, cx);
  971        assert_eq!(
  972            editor.display_text(cx),
  973            editor.buffer.read(cx).read(cx).text()
  974        );
  975    });
  976}
  977
  978#[gpui::test]
  979fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
  980    init_test(cx, |_| {});
  981
  982    let editor = cx.add_window(|window, cx| {
  983        let buffer = MultiBuffer::build_simple(
  984            &"
  985                class Foo:
  986                    # Hello!
  987
  988                    def a():
  989                        print(1)
  990
  991                    def b():
  992                        print(2)
  993
  994                    def c():
  995                        print(3)
  996            "
  997            .unindent(),
  998            cx,
  999        );
 1000        build_editor(buffer, window, cx)
 1001    });
 1002
 1003    _ = editor.update(cx, |editor, window, cx| {
 1004        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1005            s.select_display_ranges([
 1006                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
 1007            ]);
 1008        });
 1009        editor.fold(&Fold, window, cx);
 1010        assert_eq!(
 1011            editor.display_text(cx),
 1012            "
 1013                class Foo:
 1014                    # Hello!
 1015
 1016                    def a():
 1017                        print(1)
 1018
 1019                    def b():⋯
 1020
 1021                    def c():⋯
 1022            "
 1023            .unindent(),
 1024        );
 1025
 1026        editor.fold(&Fold, window, cx);
 1027        assert_eq!(
 1028            editor.display_text(cx),
 1029            "
 1030                class Foo:⋯
 1031            "
 1032            .unindent(),
 1033        );
 1034
 1035        editor.unfold_lines(&UnfoldLines, window, cx);
 1036        assert_eq!(
 1037            editor.display_text(cx),
 1038            "
 1039                class Foo:
 1040                    # Hello!
 1041
 1042                    def a():
 1043                        print(1)
 1044
 1045                    def b():⋯
 1046
 1047                    def c():⋯
 1048            "
 1049            .unindent(),
 1050        );
 1051
 1052        editor.unfold_lines(&UnfoldLines, window, cx);
 1053        assert_eq!(
 1054            editor.display_text(cx),
 1055            editor.buffer.read(cx).read(cx).text()
 1056        );
 1057    });
 1058}
 1059
 1060#[gpui::test]
 1061fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
 1062    init_test(cx, |_| {});
 1063
 1064    let editor = cx.add_window(|window, cx| {
 1065        let buffer = MultiBuffer::build_simple(
 1066            &"
 1067                class Foo:
 1068                    # Hello!
 1069
 1070                    def a():
 1071                        print(1)
 1072
 1073                    def b():
 1074                        print(2)
 1075
 1076
 1077                    def c():
 1078                        print(3)
 1079
 1080
 1081            "
 1082            .unindent(),
 1083            cx,
 1084        );
 1085        build_editor(buffer, window, cx)
 1086    });
 1087
 1088    _ = editor.update(cx, |editor, window, cx| {
 1089        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1090            s.select_display_ranges([
 1091                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
 1092            ]);
 1093        });
 1094        editor.fold(&Fold, window, cx);
 1095        assert_eq!(
 1096            editor.display_text(cx),
 1097            "
 1098                class Foo:
 1099                    # Hello!
 1100
 1101                    def a():
 1102                        print(1)
 1103
 1104                    def b():⋯
 1105
 1106
 1107                    def c():⋯
 1108
 1109
 1110            "
 1111            .unindent(),
 1112        );
 1113
 1114        editor.fold(&Fold, window, cx);
 1115        assert_eq!(
 1116            editor.display_text(cx),
 1117            "
 1118                class Foo:⋯
 1119
 1120
 1121            "
 1122            .unindent(),
 1123        );
 1124
 1125        editor.unfold_lines(&UnfoldLines, window, cx);
 1126        assert_eq!(
 1127            editor.display_text(cx),
 1128            "
 1129                class Foo:
 1130                    # Hello!
 1131
 1132                    def a():
 1133                        print(1)
 1134
 1135                    def b():⋯
 1136
 1137
 1138                    def c():⋯
 1139
 1140
 1141            "
 1142            .unindent(),
 1143        );
 1144
 1145        editor.unfold_lines(&UnfoldLines, window, cx);
 1146        assert_eq!(
 1147            editor.display_text(cx),
 1148            editor.buffer.read(cx).read(cx).text()
 1149        );
 1150    });
 1151}
 1152
 1153#[gpui::test]
 1154fn test_fold_at_level(cx: &mut TestAppContext) {
 1155    init_test(cx, |_| {});
 1156
 1157    let editor = cx.add_window(|window, cx| {
 1158        let buffer = MultiBuffer::build_simple(
 1159            &"
 1160                class Foo:
 1161                    # Hello!
 1162
 1163                    def a():
 1164                        print(1)
 1165
 1166                    def b():
 1167                        print(2)
 1168
 1169
 1170                class Bar:
 1171                    # World!
 1172
 1173                    def a():
 1174                        print(1)
 1175
 1176                    def b():
 1177                        print(2)
 1178
 1179
 1180            "
 1181            .unindent(),
 1182            cx,
 1183        );
 1184        build_editor(buffer, window, cx)
 1185    });
 1186
 1187    _ = editor.update(cx, |editor, window, cx| {
 1188        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1189        assert_eq!(
 1190            editor.display_text(cx),
 1191            "
 1192                class Foo:
 1193                    # Hello!
 1194
 1195                    def a():⋯
 1196
 1197                    def b():⋯
 1198
 1199
 1200                class Bar:
 1201                    # World!
 1202
 1203                    def a():⋯
 1204
 1205                    def b():⋯
 1206
 1207
 1208            "
 1209            .unindent(),
 1210        );
 1211
 1212        editor.fold_at_level(&FoldAtLevel(1), window, cx);
 1213        assert_eq!(
 1214            editor.display_text(cx),
 1215            "
 1216                class Foo:⋯
 1217
 1218
 1219                class Bar:⋯
 1220
 1221
 1222            "
 1223            .unindent(),
 1224        );
 1225
 1226        editor.unfold_all(&UnfoldAll, window, cx);
 1227        editor.fold_at_level(&FoldAtLevel(0), window, cx);
 1228        assert_eq!(
 1229            editor.display_text(cx),
 1230            "
 1231                class Foo:
 1232                    # Hello!
 1233
 1234                    def a():
 1235                        print(1)
 1236
 1237                    def b():
 1238                        print(2)
 1239
 1240
 1241                class Bar:
 1242                    # World!
 1243
 1244                    def a():
 1245                        print(1)
 1246
 1247                    def b():
 1248                        print(2)
 1249
 1250
 1251            "
 1252            .unindent(),
 1253        );
 1254
 1255        assert_eq!(
 1256            editor.display_text(cx),
 1257            editor.buffer.read(cx).read(cx).text()
 1258        );
 1259        let (_, positions) = marked_text_ranges(
 1260            &"
 1261                       class Foo:
 1262                           # Hello!
 1263
 1264                           def a():
 1265                              print(1)
 1266
 1267                           def b():
 1268                               p«riˇ»nt(2)
 1269
 1270
 1271                       class Bar:
 1272                           # World!
 1273
 1274                           def a():
 1275                               «ˇprint(1)
 1276
 1277                           def b():
 1278                               print(2)»
 1279
 1280
 1281                   "
 1282            .unindent(),
 1283            true,
 1284        );
 1285
 1286        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 1287            s.select_ranges(positions)
 1288        });
 1289
 1290        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1291        assert_eq!(
 1292            editor.display_text(cx),
 1293            "
 1294                class Foo:
 1295                    # Hello!
 1296
 1297                    def a():⋯
 1298
 1299                    def b():
 1300                        print(2)
 1301
 1302
 1303                class Bar:
 1304                    # World!
 1305
 1306                    def a():
 1307                        print(1)
 1308
 1309                    def b():
 1310                        print(2)
 1311
 1312
 1313            "
 1314            .unindent(),
 1315        );
 1316    });
 1317}
 1318
 1319#[gpui::test]
 1320fn test_move_cursor(cx: &mut TestAppContext) {
 1321    init_test(cx, |_| {});
 1322
 1323    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
 1324    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
 1325
 1326    buffer.update(cx, |buffer, cx| {
 1327        buffer.edit(
 1328            vec![
 1329                (Point::new(1, 0)..Point::new(1, 0), "\t"),
 1330                (Point::new(1, 1)..Point::new(1, 1), "\t"),
 1331            ],
 1332            None,
 1333            cx,
 1334        );
 1335    });
 1336    _ = editor.update(cx, |editor, window, cx| {
 1337        assert_eq!(
 1338            editor.selections.display_ranges(cx),
 1339            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1340        );
 1341
 1342        editor.move_down(&MoveDown, window, cx);
 1343        assert_eq!(
 1344            editor.selections.display_ranges(cx),
 1345            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1346        );
 1347
 1348        editor.move_right(&MoveRight, window, cx);
 1349        assert_eq!(
 1350            editor.selections.display_ranges(cx),
 1351            &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
 1352        );
 1353
 1354        editor.move_left(&MoveLeft, window, cx);
 1355        assert_eq!(
 1356            editor.selections.display_ranges(cx),
 1357            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1358        );
 1359
 1360        editor.move_up(&MoveUp, window, cx);
 1361        assert_eq!(
 1362            editor.selections.display_ranges(cx),
 1363            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1364        );
 1365
 1366        editor.move_to_end(&MoveToEnd, window, cx);
 1367        assert_eq!(
 1368            editor.selections.display_ranges(cx),
 1369            &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
 1370        );
 1371
 1372        editor.move_to_beginning(&MoveToBeginning, window, cx);
 1373        assert_eq!(
 1374            editor.selections.display_ranges(cx),
 1375            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1376        );
 1377
 1378        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1379            s.select_display_ranges([
 1380                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
 1381            ]);
 1382        });
 1383        editor.select_to_beginning(&SelectToBeginning, window, cx);
 1384        assert_eq!(
 1385            editor.selections.display_ranges(cx),
 1386            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
 1387        );
 1388
 1389        editor.select_to_end(&SelectToEnd, window, cx);
 1390        assert_eq!(
 1391            editor.selections.display_ranges(cx),
 1392            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
 1393        );
 1394    });
 1395}
 1396
 1397#[gpui::test]
 1398fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 1399    init_test(cx, |_| {});
 1400
 1401    let editor = cx.add_window(|window, cx| {
 1402        let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
 1403        build_editor(buffer, window, cx)
 1404    });
 1405
 1406    assert_eq!('🟥'.len_utf8(), 4);
 1407    assert_eq!('α'.len_utf8(), 2);
 1408
 1409    _ = editor.update(cx, |editor, window, cx| {
 1410        editor.fold_creases(
 1411            vec![
 1412                Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
 1413                Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
 1414                Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
 1415            ],
 1416            true,
 1417            window,
 1418            cx,
 1419        );
 1420        assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
 1421
 1422        editor.move_right(&MoveRight, window, cx);
 1423        assert_eq!(
 1424            editor.selections.display_ranges(cx),
 1425            &[empty_range(0, "🟥".len())]
 1426        );
 1427        editor.move_right(&MoveRight, window, cx);
 1428        assert_eq!(
 1429            editor.selections.display_ranges(cx),
 1430            &[empty_range(0, "🟥🟧".len())]
 1431        );
 1432        editor.move_right(&MoveRight, window, cx);
 1433        assert_eq!(
 1434            editor.selections.display_ranges(cx),
 1435            &[empty_range(0, "🟥🟧⋯".len())]
 1436        );
 1437
 1438        editor.move_down(&MoveDown, window, cx);
 1439        assert_eq!(
 1440            editor.selections.display_ranges(cx),
 1441            &[empty_range(1, "ab⋯e".len())]
 1442        );
 1443        editor.move_left(&MoveLeft, window, cx);
 1444        assert_eq!(
 1445            editor.selections.display_ranges(cx),
 1446            &[empty_range(1, "ab⋯".len())]
 1447        );
 1448        editor.move_left(&MoveLeft, window, cx);
 1449        assert_eq!(
 1450            editor.selections.display_ranges(cx),
 1451            &[empty_range(1, "ab".len())]
 1452        );
 1453        editor.move_left(&MoveLeft, window, cx);
 1454        assert_eq!(
 1455            editor.selections.display_ranges(cx),
 1456            &[empty_range(1, "a".len())]
 1457        );
 1458
 1459        editor.move_down(&MoveDown, window, cx);
 1460        assert_eq!(
 1461            editor.selections.display_ranges(cx),
 1462            &[empty_range(2, "α".len())]
 1463        );
 1464        editor.move_right(&MoveRight, window, cx);
 1465        assert_eq!(
 1466            editor.selections.display_ranges(cx),
 1467            &[empty_range(2, "αβ".len())]
 1468        );
 1469        editor.move_right(&MoveRight, window, cx);
 1470        assert_eq!(
 1471            editor.selections.display_ranges(cx),
 1472            &[empty_range(2, "αβ⋯".len())]
 1473        );
 1474        editor.move_right(&MoveRight, window, cx);
 1475        assert_eq!(
 1476            editor.selections.display_ranges(cx),
 1477            &[empty_range(2, "αβ⋯ε".len())]
 1478        );
 1479
 1480        editor.move_up(&MoveUp, window, cx);
 1481        assert_eq!(
 1482            editor.selections.display_ranges(cx),
 1483            &[empty_range(1, "ab⋯e".len())]
 1484        );
 1485        editor.move_down(&MoveDown, window, cx);
 1486        assert_eq!(
 1487            editor.selections.display_ranges(cx),
 1488            &[empty_range(2, "αβ⋯ε".len())]
 1489        );
 1490        editor.move_up(&MoveUp, window, cx);
 1491        assert_eq!(
 1492            editor.selections.display_ranges(cx),
 1493            &[empty_range(1, "ab⋯e".len())]
 1494        );
 1495
 1496        editor.move_up(&MoveUp, window, cx);
 1497        assert_eq!(
 1498            editor.selections.display_ranges(cx),
 1499            &[empty_range(0, "🟥🟧".len())]
 1500        );
 1501        editor.move_left(&MoveLeft, window, cx);
 1502        assert_eq!(
 1503            editor.selections.display_ranges(cx),
 1504            &[empty_range(0, "🟥".len())]
 1505        );
 1506        editor.move_left(&MoveLeft, window, cx);
 1507        assert_eq!(
 1508            editor.selections.display_ranges(cx),
 1509            &[empty_range(0, "".len())]
 1510        );
 1511    });
 1512}
 1513
 1514#[gpui::test]
 1515fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
 1516    init_test(cx, |_| {});
 1517
 1518    let editor = cx.add_window(|window, cx| {
 1519        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
 1520        build_editor(buffer, window, cx)
 1521    });
 1522    _ = editor.update(cx, |editor, window, cx| {
 1523        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1524            s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
 1525        });
 1526
 1527        // moving above start of document should move selection to start of document,
 1528        // but the next move down should still be at the original goal_x
 1529        editor.move_up(&MoveUp, window, cx);
 1530        assert_eq!(
 1531            editor.selections.display_ranges(cx),
 1532            &[empty_range(0, "".len())]
 1533        );
 1534
 1535        editor.move_down(&MoveDown, window, cx);
 1536        assert_eq!(
 1537            editor.selections.display_ranges(cx),
 1538            &[empty_range(1, "abcd".len())]
 1539        );
 1540
 1541        editor.move_down(&MoveDown, window, cx);
 1542        assert_eq!(
 1543            editor.selections.display_ranges(cx),
 1544            &[empty_range(2, "αβγ".len())]
 1545        );
 1546
 1547        editor.move_down(&MoveDown, window, cx);
 1548        assert_eq!(
 1549            editor.selections.display_ranges(cx),
 1550            &[empty_range(3, "abcd".len())]
 1551        );
 1552
 1553        editor.move_down(&MoveDown, window, cx);
 1554        assert_eq!(
 1555            editor.selections.display_ranges(cx),
 1556            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1557        );
 1558
 1559        // moving past end of document should not change goal_x
 1560        editor.move_down(&MoveDown, window, cx);
 1561        assert_eq!(
 1562            editor.selections.display_ranges(cx),
 1563            &[empty_range(5, "".len())]
 1564        );
 1565
 1566        editor.move_down(&MoveDown, window, cx);
 1567        assert_eq!(
 1568            editor.selections.display_ranges(cx),
 1569            &[empty_range(5, "".len())]
 1570        );
 1571
 1572        editor.move_up(&MoveUp, window, cx);
 1573        assert_eq!(
 1574            editor.selections.display_ranges(cx),
 1575            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1576        );
 1577
 1578        editor.move_up(&MoveUp, window, cx);
 1579        assert_eq!(
 1580            editor.selections.display_ranges(cx),
 1581            &[empty_range(3, "abcd".len())]
 1582        );
 1583
 1584        editor.move_up(&MoveUp, window, cx);
 1585        assert_eq!(
 1586            editor.selections.display_ranges(cx),
 1587            &[empty_range(2, "αβγ".len())]
 1588        );
 1589    });
 1590}
 1591
 1592#[gpui::test]
 1593fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 1594    init_test(cx, |_| {});
 1595    let move_to_beg = MoveToBeginningOfLine {
 1596        stop_at_soft_wraps: true,
 1597        stop_at_indent: true,
 1598    };
 1599
 1600    let delete_to_beg = DeleteToBeginningOfLine {
 1601        stop_at_indent: false,
 1602    };
 1603
 1604    let move_to_end = MoveToEndOfLine {
 1605        stop_at_soft_wraps: true,
 1606    };
 1607
 1608    let editor = cx.add_window(|window, cx| {
 1609        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1610        build_editor(buffer, window, cx)
 1611    });
 1612    _ = editor.update(cx, |editor, window, cx| {
 1613        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1614            s.select_display_ranges([
 1615                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1616                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1617            ]);
 1618        });
 1619    });
 1620
 1621    _ = editor.update(cx, |editor, window, cx| {
 1622        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1623        assert_eq!(
 1624            editor.selections.display_ranges(cx),
 1625            &[
 1626                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1627                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1628            ]
 1629        );
 1630    });
 1631
 1632    _ = editor.update(cx, |editor, window, cx| {
 1633        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1634        assert_eq!(
 1635            editor.selections.display_ranges(cx),
 1636            &[
 1637                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1638                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1639            ]
 1640        );
 1641    });
 1642
 1643    _ = editor.update(cx, |editor, window, cx| {
 1644        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1645        assert_eq!(
 1646            editor.selections.display_ranges(cx),
 1647            &[
 1648                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1649                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1650            ]
 1651        );
 1652    });
 1653
 1654    _ = editor.update(cx, |editor, window, cx| {
 1655        editor.move_to_end_of_line(&move_to_end, window, cx);
 1656        assert_eq!(
 1657            editor.selections.display_ranges(cx),
 1658            &[
 1659                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1660                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1661            ]
 1662        );
 1663    });
 1664
 1665    // Moving to the end of line again is a no-op.
 1666    _ = editor.update(cx, |editor, window, cx| {
 1667        editor.move_to_end_of_line(&move_to_end, window, cx);
 1668        assert_eq!(
 1669            editor.selections.display_ranges(cx),
 1670            &[
 1671                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1672                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1673            ]
 1674        );
 1675    });
 1676
 1677    _ = editor.update(cx, |editor, window, cx| {
 1678        editor.move_left(&MoveLeft, window, cx);
 1679        editor.select_to_beginning_of_line(
 1680            &SelectToBeginningOfLine {
 1681                stop_at_soft_wraps: true,
 1682                stop_at_indent: true,
 1683            },
 1684            window,
 1685            cx,
 1686        );
 1687        assert_eq!(
 1688            editor.selections.display_ranges(cx),
 1689            &[
 1690                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1691                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1692            ]
 1693        );
 1694    });
 1695
 1696    _ = editor.update(cx, |editor, window, cx| {
 1697        editor.select_to_beginning_of_line(
 1698            &SelectToBeginningOfLine {
 1699                stop_at_soft_wraps: true,
 1700                stop_at_indent: true,
 1701            },
 1702            window,
 1703            cx,
 1704        );
 1705        assert_eq!(
 1706            editor.selections.display_ranges(cx),
 1707            &[
 1708                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1709                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1710            ]
 1711        );
 1712    });
 1713
 1714    _ = editor.update(cx, |editor, window, cx| {
 1715        editor.select_to_beginning_of_line(
 1716            &SelectToBeginningOfLine {
 1717                stop_at_soft_wraps: true,
 1718                stop_at_indent: true,
 1719            },
 1720            window,
 1721            cx,
 1722        );
 1723        assert_eq!(
 1724            editor.selections.display_ranges(cx),
 1725            &[
 1726                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1727                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1728            ]
 1729        );
 1730    });
 1731
 1732    _ = editor.update(cx, |editor, window, cx| {
 1733        editor.select_to_end_of_line(
 1734            &SelectToEndOfLine {
 1735                stop_at_soft_wraps: true,
 1736            },
 1737            window,
 1738            cx,
 1739        );
 1740        assert_eq!(
 1741            editor.selections.display_ranges(cx),
 1742            &[
 1743                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
 1744                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
 1745            ]
 1746        );
 1747    });
 1748
 1749    _ = editor.update(cx, |editor, window, cx| {
 1750        editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
 1751        assert_eq!(editor.display_text(cx), "ab\n  de");
 1752        assert_eq!(
 1753            editor.selections.display_ranges(cx),
 1754            &[
 1755                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 1756                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1757            ]
 1758        );
 1759    });
 1760
 1761    _ = editor.update(cx, |editor, window, cx| {
 1762        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1763        assert_eq!(editor.display_text(cx), "\n");
 1764        assert_eq!(
 1765            editor.selections.display_ranges(cx),
 1766            &[
 1767                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1768                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1769            ]
 1770        );
 1771    });
 1772}
 1773
 1774#[gpui::test]
 1775fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
 1776    init_test(cx, |_| {});
 1777    let move_to_beg = MoveToBeginningOfLine {
 1778        stop_at_soft_wraps: false,
 1779        stop_at_indent: false,
 1780    };
 1781
 1782    let move_to_end = MoveToEndOfLine {
 1783        stop_at_soft_wraps: false,
 1784    };
 1785
 1786    let editor = cx.add_window(|window, cx| {
 1787        let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
 1788        build_editor(buffer, window, cx)
 1789    });
 1790
 1791    _ = editor.update(cx, |editor, window, cx| {
 1792        editor.set_wrap_width(Some(140.0.into()), cx);
 1793
 1794        // We expect the following lines after wrapping
 1795        // ```
 1796        // thequickbrownfox
 1797        // jumpedoverthelazydo
 1798        // gs
 1799        // ```
 1800        // The final `gs` was soft-wrapped onto a new line.
 1801        assert_eq!(
 1802            "thequickbrownfox\njumpedoverthelaz\nydogs",
 1803            editor.display_text(cx),
 1804        );
 1805
 1806        // First, let's assert behavior on the first line, that was not soft-wrapped.
 1807        // Start the cursor at the `k` on the first line
 1808        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1809            s.select_display_ranges([
 1810                DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
 1811            ]);
 1812        });
 1813
 1814        // Moving to the beginning of the line should put us at the beginning of the line.
 1815        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1816        assert_eq!(
 1817            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
 1818            editor.selections.display_ranges(cx)
 1819        );
 1820
 1821        // Moving to the end of the line should put us at the end of the line.
 1822        editor.move_to_end_of_line(&move_to_end, window, cx);
 1823        assert_eq!(
 1824            vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
 1825            editor.selections.display_ranges(cx)
 1826        );
 1827
 1828        // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
 1829        // Start the cursor at the last line (`y` that was wrapped to a new line)
 1830        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1831            s.select_display_ranges([
 1832                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
 1833            ]);
 1834        });
 1835
 1836        // Moving to the beginning of the line should put us at the start of the second line of
 1837        // display text, i.e., the `j`.
 1838        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1839        assert_eq!(
 1840            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1841            editor.selections.display_ranges(cx)
 1842        );
 1843
 1844        // Moving to the beginning of the line again should be a no-op.
 1845        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1846        assert_eq!(
 1847            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1848            editor.selections.display_ranges(cx)
 1849        );
 1850
 1851        // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
 1852        // next display line.
 1853        editor.move_to_end_of_line(&move_to_end, window, cx);
 1854        assert_eq!(
 1855            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1856            editor.selections.display_ranges(cx)
 1857        );
 1858
 1859        // Moving to the end of the line again should be a no-op.
 1860        editor.move_to_end_of_line(&move_to_end, window, cx);
 1861        assert_eq!(
 1862            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1863            editor.selections.display_ranges(cx)
 1864        );
 1865    });
 1866}
 1867
 1868#[gpui::test]
 1869fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
 1870    init_test(cx, |_| {});
 1871
 1872    let move_to_beg = MoveToBeginningOfLine {
 1873        stop_at_soft_wraps: true,
 1874        stop_at_indent: true,
 1875    };
 1876
 1877    let select_to_beg = SelectToBeginningOfLine {
 1878        stop_at_soft_wraps: true,
 1879        stop_at_indent: true,
 1880    };
 1881
 1882    let delete_to_beg = DeleteToBeginningOfLine {
 1883        stop_at_indent: true,
 1884    };
 1885
 1886    let move_to_end = MoveToEndOfLine {
 1887        stop_at_soft_wraps: false,
 1888    };
 1889
 1890    let editor = cx.add_window(|window, cx| {
 1891        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1892        build_editor(buffer, window, cx)
 1893    });
 1894
 1895    _ = editor.update(cx, |editor, window, cx| {
 1896        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1897            s.select_display_ranges([
 1898                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1899                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1900            ]);
 1901        });
 1902
 1903        // Moving to the beginning of the line should put the first cursor at the beginning of the line,
 1904        // and the second cursor at the first non-whitespace character in the line.
 1905        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1906        assert_eq!(
 1907            editor.selections.display_ranges(cx),
 1908            &[
 1909                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1910                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1911            ]
 1912        );
 1913
 1914        // Moving to the beginning of the line again should be a no-op for the first cursor,
 1915        // and should move the second cursor to the beginning of the line.
 1916        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1917        assert_eq!(
 1918            editor.selections.display_ranges(cx),
 1919            &[
 1920                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1921                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1922            ]
 1923        );
 1924
 1925        // Moving to the beginning of the line again should still be a no-op for the first cursor,
 1926        // and should move the second cursor back to the first non-whitespace character in the line.
 1927        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1928        assert_eq!(
 1929            editor.selections.display_ranges(cx),
 1930            &[
 1931                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1932                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1933            ]
 1934        );
 1935
 1936        // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
 1937        // and to the first non-whitespace character in the line for the second cursor.
 1938        editor.move_to_end_of_line(&move_to_end, window, cx);
 1939        editor.move_left(&MoveLeft, window, cx);
 1940        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1941        assert_eq!(
 1942            editor.selections.display_ranges(cx),
 1943            &[
 1944                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1945                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1946            ]
 1947        );
 1948
 1949        // Selecting to the beginning of the line again should be a no-op for the first cursor,
 1950        // and should select to the beginning of the line for the second cursor.
 1951        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1952        assert_eq!(
 1953            editor.selections.display_ranges(cx),
 1954            &[
 1955                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1956                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1957            ]
 1958        );
 1959
 1960        // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
 1961        // and should delete to the first non-whitespace character in the line for the second cursor.
 1962        editor.move_to_end_of_line(&move_to_end, window, cx);
 1963        editor.move_left(&MoveLeft, window, cx);
 1964        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1965        assert_eq!(editor.text(cx), "c\n  f");
 1966    });
 1967}
 1968
 1969#[gpui::test]
 1970fn test_beginning_of_line_with_cursor_between_line_start_and_indent(cx: &mut TestAppContext) {
 1971    init_test(cx, |_| {});
 1972
 1973    let move_to_beg = MoveToBeginningOfLine {
 1974        stop_at_soft_wraps: true,
 1975        stop_at_indent: true,
 1976    };
 1977
 1978    let editor = cx.add_window(|window, cx| {
 1979        let buffer = MultiBuffer::build_simple("    hello\nworld", cx);
 1980        build_editor(buffer, window, cx)
 1981    });
 1982
 1983    _ = editor.update(cx, |editor, window, cx| {
 1984        // test cursor between line_start and indent_start
 1985        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1986            s.select_display_ranges([
 1987                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3)
 1988            ]);
 1989        });
 1990
 1991        // cursor should move to line_start
 1992        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1993        assert_eq!(
 1994            editor.selections.display_ranges(cx),
 1995            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1996        );
 1997
 1998        // cursor should move to indent_start
 1999        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2000        assert_eq!(
 2001            editor.selections.display_ranges(cx),
 2002            &[DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 4)]
 2003        );
 2004
 2005        // cursor should move to back to line_start
 2006        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2007        assert_eq!(
 2008            editor.selections.display_ranges(cx),
 2009            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 2010        );
 2011    });
 2012}
 2013
 2014#[gpui::test]
 2015fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 2016    init_test(cx, |_| {});
 2017
 2018    let editor = cx.add_window(|window, cx| {
 2019        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
 2020        build_editor(buffer, window, cx)
 2021    });
 2022    _ = editor.update(cx, |editor, window, cx| {
 2023        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2024            s.select_display_ranges([
 2025                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
 2026                DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
 2027            ])
 2028        });
 2029        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2030        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 2031
 2032        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2033        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\nˇ  {baz.qux()}", editor, cx);
 2034
 2035        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2036        assert_selection_ranges("use ˇstd::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 2037
 2038        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2039        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 2040
 2041        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2042        assert_selection_ranges("ˇuse std::str::{foo, ˇbar}\n\n  {baz.qux()}", editor, cx);
 2043
 2044        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2045        assert_selection_ranges("useˇ std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 2046
 2047        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2048        assert_selection_ranges("use stdˇ::str::{foo, bar}ˇ\n\n  {baz.qux()}", editor, cx);
 2049
 2050        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2051        assert_selection_ranges("use std::ˇstr::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 2052
 2053        editor.move_right(&MoveRight, window, cx);
 2054        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 2055        assert_selection_ranges(
 2056            "use std::«ˇs»tr::{foo, bar}\n«ˇ\n»  {baz.qux()}",
 2057            editor,
 2058            cx,
 2059        );
 2060
 2061        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 2062        assert_selection_ranges(
 2063            "use std«ˇ::s»tr::{foo, bar«ˇ}\n\n»  {baz.qux()}",
 2064            editor,
 2065            cx,
 2066        );
 2067
 2068        editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
 2069        assert_selection_ranges(
 2070            "use std::«ˇs»tr::{foo, bar}«ˇ\n\n»  {baz.qux()}",
 2071            editor,
 2072            cx,
 2073        );
 2074    });
 2075}
 2076
 2077#[gpui::test]
 2078fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 2079    init_test(cx, |_| {});
 2080
 2081    let editor = cx.add_window(|window, cx| {
 2082        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 2083        build_editor(buffer, window, cx)
 2084    });
 2085
 2086    _ = editor.update(cx, |editor, window, cx| {
 2087        editor.set_wrap_width(Some(140.0.into()), cx);
 2088        assert_eq!(
 2089            editor.display_text(cx),
 2090            "use one::{\n    two::three::\n    four::five\n};"
 2091        );
 2092
 2093        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2094            s.select_display_ranges([
 2095                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
 2096            ]);
 2097        });
 2098
 2099        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2100        assert_eq!(
 2101            editor.selections.display_ranges(cx),
 2102            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
 2103        );
 2104
 2105        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2106        assert_eq!(
 2107            editor.selections.display_ranges(cx),
 2108            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2109        );
 2110
 2111        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2112        assert_eq!(
 2113            editor.selections.display_ranges(cx),
 2114            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2115        );
 2116
 2117        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2118        assert_eq!(
 2119            editor.selections.display_ranges(cx),
 2120            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
 2121        );
 2122
 2123        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2124        assert_eq!(
 2125            editor.selections.display_ranges(cx),
 2126            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2127        );
 2128
 2129        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2130        assert_eq!(
 2131            editor.selections.display_ranges(cx),
 2132            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2133        );
 2134    });
 2135}
 2136
 2137#[gpui::test]
 2138async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
 2139    init_test(cx, |_| {});
 2140    let mut cx = EditorTestContext::new(cx).await;
 2141
 2142    let line_height = cx.editor(|editor, window, _| {
 2143        editor
 2144            .style()
 2145            .unwrap()
 2146            .text
 2147            .line_height_in_pixels(window.rem_size())
 2148    });
 2149    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2150
 2151    cx.set_state(
 2152        &r#"ˇone
 2153        two
 2154
 2155        three
 2156        fourˇ
 2157        five
 2158
 2159        six"#
 2160            .unindent(),
 2161    );
 2162
 2163    cx.update_editor(|editor, window, cx| {
 2164        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2165    });
 2166    cx.assert_editor_state(
 2167        &r#"one
 2168        two
 2169        ˇ
 2170        three
 2171        four
 2172        five
 2173        ˇ
 2174        six"#
 2175            .unindent(),
 2176    );
 2177
 2178    cx.update_editor(|editor, window, cx| {
 2179        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2180    });
 2181    cx.assert_editor_state(
 2182        &r#"one
 2183        two
 2184
 2185        three
 2186        four
 2187        five
 2188        ˇ
 2189        sixˇ"#
 2190            .unindent(),
 2191    );
 2192
 2193    cx.update_editor(|editor, window, cx| {
 2194        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2195    });
 2196    cx.assert_editor_state(
 2197        &r#"one
 2198        two
 2199
 2200        three
 2201        four
 2202        five
 2203
 2204        sixˇ"#
 2205            .unindent(),
 2206    );
 2207
 2208    cx.update_editor(|editor, window, cx| {
 2209        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2210    });
 2211    cx.assert_editor_state(
 2212        &r#"one
 2213        two
 2214
 2215        three
 2216        four
 2217        five
 2218        ˇ
 2219        six"#
 2220            .unindent(),
 2221    );
 2222
 2223    cx.update_editor(|editor, window, cx| {
 2224        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2225    });
 2226    cx.assert_editor_state(
 2227        &r#"one
 2228        two
 2229        ˇ
 2230        three
 2231        four
 2232        five
 2233
 2234        six"#
 2235            .unindent(),
 2236    );
 2237
 2238    cx.update_editor(|editor, window, cx| {
 2239        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2240    });
 2241    cx.assert_editor_state(
 2242        &r#"ˇone
 2243        two
 2244
 2245        three
 2246        four
 2247        five
 2248
 2249        six"#
 2250            .unindent(),
 2251    );
 2252}
 2253
 2254#[gpui::test]
 2255async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2256    init_test(cx, |_| {});
 2257    let mut cx = EditorTestContext::new(cx).await;
 2258    let line_height = cx.editor(|editor, window, _| {
 2259        editor
 2260            .style()
 2261            .unwrap()
 2262            .text
 2263            .line_height_in_pixels(window.rem_size())
 2264    });
 2265    let window = cx.window;
 2266    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2267
 2268    cx.set_state(
 2269        r#"ˇone
 2270        two
 2271        three
 2272        four
 2273        five
 2274        six
 2275        seven
 2276        eight
 2277        nine
 2278        ten
 2279        "#,
 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.)
 2286        );
 2287        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2288        assert_eq!(
 2289            editor.snapshot(window, cx).scroll_position(),
 2290            gpui::Point::new(0., 3.)
 2291        );
 2292        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2293        assert_eq!(
 2294            editor.snapshot(window, cx).scroll_position(),
 2295            gpui::Point::new(0., 6.)
 2296        );
 2297        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2298        assert_eq!(
 2299            editor.snapshot(window, cx).scroll_position(),
 2300            gpui::Point::new(0., 3.)
 2301        );
 2302
 2303        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2304        assert_eq!(
 2305            editor.snapshot(window, cx).scroll_position(),
 2306            gpui::Point::new(0., 1.)
 2307        );
 2308        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2309        assert_eq!(
 2310            editor.snapshot(window, cx).scroll_position(),
 2311            gpui::Point::new(0., 3.)
 2312        );
 2313    });
 2314}
 2315
 2316#[gpui::test]
 2317async fn test_autoscroll(cx: &mut TestAppContext) {
 2318    init_test(cx, |_| {});
 2319    let mut cx = EditorTestContext::new(cx).await;
 2320
 2321    let line_height = cx.update_editor(|editor, window, cx| {
 2322        editor.set_vertical_scroll_margin(2, cx);
 2323        editor
 2324            .style()
 2325            .unwrap()
 2326            .text
 2327            .line_height_in_pixels(window.rem_size())
 2328    });
 2329    let window = cx.window;
 2330    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2331
 2332    cx.set_state(
 2333        r#"ˇone
 2334            two
 2335            three
 2336            four
 2337            five
 2338            six
 2339            seven
 2340            eight
 2341            nine
 2342            ten
 2343        "#,
 2344    );
 2345    cx.update_editor(|editor, window, cx| {
 2346        assert_eq!(
 2347            editor.snapshot(window, cx).scroll_position(),
 2348            gpui::Point::new(0., 0.0)
 2349        );
 2350    });
 2351
 2352    // Add a cursor below the visible area. Since both cursors cannot fit
 2353    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2354    // allows the vertical scroll margin below that cursor.
 2355    cx.update_editor(|editor, window, cx| {
 2356        editor.change_selections(Default::default(), window, cx, |selections| {
 2357            selections.select_ranges([
 2358                Point::new(0, 0)..Point::new(0, 0),
 2359                Point::new(6, 0)..Point::new(6, 0),
 2360            ]);
 2361        })
 2362    });
 2363    cx.update_editor(|editor, window, cx| {
 2364        assert_eq!(
 2365            editor.snapshot(window, cx).scroll_position(),
 2366            gpui::Point::new(0., 3.0)
 2367        );
 2368    });
 2369
 2370    // Move down. The editor cursor scrolls down to track the newest cursor.
 2371    cx.update_editor(|editor, window, cx| {
 2372        editor.move_down(&Default::default(), window, cx);
 2373    });
 2374    cx.update_editor(|editor, window, cx| {
 2375        assert_eq!(
 2376            editor.snapshot(window, cx).scroll_position(),
 2377            gpui::Point::new(0., 4.0)
 2378        );
 2379    });
 2380
 2381    // Add a cursor above the visible area. Since both cursors fit on screen,
 2382    // the editor scrolls to show both.
 2383    cx.update_editor(|editor, window, cx| {
 2384        editor.change_selections(Default::default(), window, cx, |selections| {
 2385            selections.select_ranges([
 2386                Point::new(1, 0)..Point::new(1, 0),
 2387                Point::new(6, 0)..Point::new(6, 0),
 2388            ]);
 2389        })
 2390    });
 2391    cx.update_editor(|editor, window, cx| {
 2392        assert_eq!(
 2393            editor.snapshot(window, cx).scroll_position(),
 2394            gpui::Point::new(0., 1.0)
 2395        );
 2396    });
 2397}
 2398
 2399#[gpui::test]
 2400async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2401    init_test(cx, |_| {});
 2402    let mut cx = EditorTestContext::new(cx).await;
 2403
 2404    let line_height = cx.editor(|editor, window, _cx| {
 2405        editor
 2406            .style()
 2407            .unwrap()
 2408            .text
 2409            .line_height_in_pixels(window.rem_size())
 2410    });
 2411    let window = cx.window;
 2412    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2413    cx.set_state(
 2414        &r#"
 2415        ˇone
 2416        two
 2417        threeˇ
 2418        four
 2419        five
 2420        six
 2421        seven
 2422        eight
 2423        nine
 2424        ten
 2425        "#
 2426        .unindent(),
 2427    );
 2428
 2429    cx.update_editor(|editor, window, cx| {
 2430        editor.move_page_down(&MovePageDown::default(), window, cx)
 2431    });
 2432    cx.assert_editor_state(
 2433        &r#"
 2434        one
 2435        two
 2436        three
 2437        ˇfour
 2438        five
 2439        sixˇ
 2440        seven
 2441        eight
 2442        nine
 2443        ten
 2444        "#
 2445        .unindent(),
 2446    );
 2447
 2448    cx.update_editor(|editor, window, cx| {
 2449        editor.move_page_down(&MovePageDown::default(), window, cx)
 2450    });
 2451    cx.assert_editor_state(
 2452        &r#"
 2453        one
 2454        two
 2455        three
 2456        four
 2457        five
 2458        six
 2459        ˇseven
 2460        eight
 2461        nineˇ
 2462        ten
 2463        "#
 2464        .unindent(),
 2465    );
 2466
 2467    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2468    cx.assert_editor_state(
 2469        &r#"
 2470        one
 2471        two
 2472        three
 2473        ˇfour
 2474        five
 2475        sixˇ
 2476        seven
 2477        eight
 2478        nine
 2479        ten
 2480        "#
 2481        .unindent(),
 2482    );
 2483
 2484    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2485    cx.assert_editor_state(
 2486        &r#"
 2487        ˇone
 2488        two
 2489        threeˇ
 2490        four
 2491        five
 2492        six
 2493        seven
 2494        eight
 2495        nine
 2496        ten
 2497        "#
 2498        .unindent(),
 2499    );
 2500
 2501    // Test select collapsing
 2502    cx.update_editor(|editor, window, cx| {
 2503        editor.move_page_down(&MovePageDown::default(), window, cx);
 2504        editor.move_page_down(&MovePageDown::default(), window, cx);
 2505        editor.move_page_down(&MovePageDown::default(), window, cx);
 2506    });
 2507    cx.assert_editor_state(
 2508        &r#"
 2509        one
 2510        two
 2511        three
 2512        four
 2513        five
 2514        six
 2515        seven
 2516        eight
 2517        nine
 2518        ˇten
 2519        ˇ"#
 2520        .unindent(),
 2521    );
 2522}
 2523
 2524#[gpui::test]
 2525async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2526    init_test(cx, |_| {});
 2527    let mut cx = EditorTestContext::new(cx).await;
 2528    cx.set_state("one «two threeˇ» four");
 2529    cx.update_editor(|editor, window, cx| {
 2530        editor.delete_to_beginning_of_line(
 2531            &DeleteToBeginningOfLine {
 2532                stop_at_indent: false,
 2533            },
 2534            window,
 2535            cx,
 2536        );
 2537        assert_eq!(editor.text(cx), " four");
 2538    });
 2539}
 2540
 2541#[gpui::test]
 2542async fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2543    init_test(cx, |_| {});
 2544
 2545    let mut cx = EditorTestContext::new(cx).await;
 2546
 2547    // For an empty selection, the preceding word fragment is deleted.
 2548    // For non-empty selections, only selected characters are deleted.
 2549    cx.set_state("onˇe two t«hreˇ»e four");
 2550    cx.update_editor(|editor, window, cx| {
 2551        editor.delete_to_previous_word_start(
 2552            &DeleteToPreviousWordStart {
 2553                ignore_newlines: false,
 2554                ignore_brackets: false,
 2555            },
 2556            window,
 2557            cx,
 2558        );
 2559    });
 2560    cx.assert_editor_state("ˇe two tˇe four");
 2561
 2562    cx.set_state("e tˇwo te «fˇ»our");
 2563    cx.update_editor(|editor, window, cx| {
 2564        editor.delete_to_next_word_end(
 2565            &DeleteToNextWordEnd {
 2566                ignore_newlines: false,
 2567                ignore_brackets: false,
 2568            },
 2569            window,
 2570            cx,
 2571        );
 2572    });
 2573    cx.assert_editor_state("e tˇ te ˇour");
 2574}
 2575
 2576#[gpui::test]
 2577async fn test_delete_whitespaces(cx: &mut TestAppContext) {
 2578    init_test(cx, |_| {});
 2579
 2580    let mut cx = EditorTestContext::new(cx).await;
 2581
 2582    cx.set_state("here is some text    ˇwith a space");
 2583    cx.update_editor(|editor, window, cx| {
 2584        editor.delete_to_previous_word_start(
 2585            &DeleteToPreviousWordStart {
 2586                ignore_newlines: false,
 2587                ignore_brackets: true,
 2588            },
 2589            window,
 2590            cx,
 2591        );
 2592    });
 2593    // Continuous whitespace sequences are removed entirely, words behind them are not affected by the deletion action.
 2594    cx.assert_editor_state("here is some textˇwith a space");
 2595
 2596    cx.set_state("here is some text    ˇwith a space");
 2597    cx.update_editor(|editor, window, cx| {
 2598        editor.delete_to_previous_word_start(
 2599            &DeleteToPreviousWordStart {
 2600                ignore_newlines: false,
 2601                ignore_brackets: false,
 2602            },
 2603            window,
 2604            cx,
 2605        );
 2606    });
 2607    cx.assert_editor_state("here is some textˇwith a space");
 2608
 2609    cx.set_state("here is some textˇ    with a space");
 2610    cx.update_editor(|editor, window, cx| {
 2611        editor.delete_to_next_word_end(
 2612            &DeleteToNextWordEnd {
 2613                ignore_newlines: false,
 2614                ignore_brackets: true,
 2615            },
 2616            window,
 2617            cx,
 2618        );
 2619    });
 2620    // Same happens in the other direction.
 2621    cx.assert_editor_state("here is some textˇwith a space");
 2622
 2623    cx.set_state("here is some textˇ    with a space");
 2624    cx.update_editor(|editor, window, cx| {
 2625        editor.delete_to_next_word_end(
 2626            &DeleteToNextWordEnd {
 2627                ignore_newlines: false,
 2628                ignore_brackets: false,
 2629            },
 2630            window,
 2631            cx,
 2632        );
 2633    });
 2634    cx.assert_editor_state("here is some textˇwith a space");
 2635
 2636    cx.set_state("here is some textˇ    with a space");
 2637    cx.update_editor(|editor, window, cx| {
 2638        editor.delete_to_next_word_end(
 2639            &DeleteToNextWordEnd {
 2640                ignore_newlines: true,
 2641                ignore_brackets: false,
 2642            },
 2643            window,
 2644            cx,
 2645        );
 2646    });
 2647    cx.assert_editor_state("here is some textˇwith a space");
 2648    cx.update_editor(|editor, window, cx| {
 2649        editor.delete_to_previous_word_start(
 2650            &DeleteToPreviousWordStart {
 2651                ignore_newlines: true,
 2652                ignore_brackets: false,
 2653            },
 2654            window,
 2655            cx,
 2656        );
 2657    });
 2658    cx.assert_editor_state("here is some ˇwith a space");
 2659    cx.update_editor(|editor, window, cx| {
 2660        editor.delete_to_previous_word_start(
 2661            &DeleteToPreviousWordStart {
 2662                ignore_newlines: true,
 2663                ignore_brackets: false,
 2664            },
 2665            window,
 2666            cx,
 2667        );
 2668    });
 2669    // Single whitespaces are removed with the word behind them.
 2670    cx.assert_editor_state("here is ˇwith a space");
 2671    cx.update_editor(|editor, window, cx| {
 2672        editor.delete_to_previous_word_start(
 2673            &DeleteToPreviousWordStart {
 2674                ignore_newlines: true,
 2675                ignore_brackets: false,
 2676            },
 2677            window,
 2678            cx,
 2679        );
 2680    });
 2681    cx.assert_editor_state("here ˇwith a space");
 2682    cx.update_editor(|editor, window, cx| {
 2683        editor.delete_to_previous_word_start(
 2684            &DeleteToPreviousWordStart {
 2685                ignore_newlines: true,
 2686                ignore_brackets: false,
 2687            },
 2688            window,
 2689            cx,
 2690        );
 2691    });
 2692    cx.assert_editor_state("ˇwith a space");
 2693    cx.update_editor(|editor, window, cx| {
 2694        editor.delete_to_previous_word_start(
 2695            &DeleteToPreviousWordStart {
 2696                ignore_newlines: true,
 2697                ignore_brackets: false,
 2698            },
 2699            window,
 2700            cx,
 2701        );
 2702    });
 2703    cx.assert_editor_state("ˇwith a space");
 2704    cx.update_editor(|editor, window, cx| {
 2705        editor.delete_to_next_word_end(
 2706            &DeleteToNextWordEnd {
 2707                ignore_newlines: true,
 2708                ignore_brackets: false,
 2709            },
 2710            window,
 2711            cx,
 2712        );
 2713    });
 2714    // Same happens in the other direction.
 2715    cx.assert_editor_state("ˇ a space");
 2716    cx.update_editor(|editor, window, cx| {
 2717        editor.delete_to_next_word_end(
 2718            &DeleteToNextWordEnd {
 2719                ignore_newlines: true,
 2720                ignore_brackets: false,
 2721            },
 2722            window,
 2723            cx,
 2724        );
 2725    });
 2726    cx.assert_editor_state("ˇ space");
 2727    cx.update_editor(|editor, window, cx| {
 2728        editor.delete_to_next_word_end(
 2729            &DeleteToNextWordEnd {
 2730                ignore_newlines: true,
 2731                ignore_brackets: false,
 2732            },
 2733            window,
 2734            cx,
 2735        );
 2736    });
 2737    cx.assert_editor_state("ˇ");
 2738    cx.update_editor(|editor, window, cx| {
 2739        editor.delete_to_next_word_end(
 2740            &DeleteToNextWordEnd {
 2741                ignore_newlines: true,
 2742                ignore_brackets: false,
 2743            },
 2744            window,
 2745            cx,
 2746        );
 2747    });
 2748    cx.assert_editor_state("ˇ");
 2749    cx.update_editor(|editor, window, cx| {
 2750        editor.delete_to_previous_word_start(
 2751            &DeleteToPreviousWordStart {
 2752                ignore_newlines: true,
 2753                ignore_brackets: false,
 2754            },
 2755            window,
 2756            cx,
 2757        );
 2758    });
 2759    cx.assert_editor_state("ˇ");
 2760}
 2761
 2762#[gpui::test]
 2763async fn test_delete_to_bracket(cx: &mut TestAppContext) {
 2764    init_test(cx, |_| {});
 2765
 2766    let language = Arc::new(
 2767        Language::new(
 2768            LanguageConfig {
 2769                brackets: BracketPairConfig {
 2770                    pairs: vec![
 2771                        BracketPair {
 2772                            start: "\"".to_string(),
 2773                            end: "\"".to_string(),
 2774                            close: true,
 2775                            surround: true,
 2776                            newline: false,
 2777                        },
 2778                        BracketPair {
 2779                            start: "(".to_string(),
 2780                            end: ")".to_string(),
 2781                            close: true,
 2782                            surround: true,
 2783                            newline: true,
 2784                        },
 2785                    ],
 2786                    ..BracketPairConfig::default()
 2787                },
 2788                ..LanguageConfig::default()
 2789            },
 2790            Some(tree_sitter_rust::LANGUAGE.into()),
 2791        )
 2792        .with_brackets_query(
 2793            r#"
 2794                ("(" @open ")" @close)
 2795                ("\"" @open "\"" @close)
 2796            "#,
 2797        )
 2798        .unwrap(),
 2799    );
 2800
 2801    let mut cx = EditorTestContext::new(cx).await;
 2802    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2803
 2804    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 2805    cx.update_editor(|editor, window, cx| {
 2806        editor.delete_to_previous_word_start(
 2807            &DeleteToPreviousWordStart {
 2808                ignore_newlines: true,
 2809                ignore_brackets: false,
 2810            },
 2811            window,
 2812            cx,
 2813        );
 2814    });
 2815    // Deletion stops before brackets if asked to not ignore them.
 2816    cx.assert_editor_state(r#"macro!("ˇCOMMENT");"#);
 2817    cx.update_editor(|editor, window, cx| {
 2818        editor.delete_to_previous_word_start(
 2819            &DeleteToPreviousWordStart {
 2820                ignore_newlines: true,
 2821                ignore_brackets: false,
 2822            },
 2823            window,
 2824            cx,
 2825        );
 2826    });
 2827    // Deletion has to remove a single bracket and then stop again.
 2828    cx.assert_editor_state(r#"macro!(ˇCOMMENT");"#);
 2829
 2830    cx.update_editor(|editor, window, cx| {
 2831        editor.delete_to_previous_word_start(
 2832            &DeleteToPreviousWordStart {
 2833                ignore_newlines: true,
 2834                ignore_brackets: false,
 2835            },
 2836            window,
 2837            cx,
 2838        );
 2839    });
 2840    cx.assert_editor_state(r#"macro!ˇCOMMENT");"#);
 2841
 2842    cx.update_editor(|editor, window, cx| {
 2843        editor.delete_to_previous_word_start(
 2844            &DeleteToPreviousWordStart {
 2845                ignore_newlines: true,
 2846                ignore_brackets: false,
 2847            },
 2848            window,
 2849            cx,
 2850        );
 2851    });
 2852    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2853
 2854    cx.update_editor(|editor, window, cx| {
 2855        editor.delete_to_previous_word_start(
 2856            &DeleteToPreviousWordStart {
 2857                ignore_newlines: true,
 2858                ignore_brackets: false,
 2859            },
 2860            window,
 2861            cx,
 2862        );
 2863    });
 2864    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2865
 2866    cx.update_editor(|editor, window, cx| {
 2867        editor.delete_to_next_word_end(
 2868            &DeleteToNextWordEnd {
 2869                ignore_newlines: true,
 2870                ignore_brackets: false,
 2871            },
 2872            window,
 2873            cx,
 2874        );
 2875    });
 2876    // Brackets on the right are not paired anymore, hence deletion does not stop at them
 2877    cx.assert_editor_state(r#"ˇ");"#);
 2878
 2879    cx.update_editor(|editor, window, cx| {
 2880        editor.delete_to_next_word_end(
 2881            &DeleteToNextWordEnd {
 2882                ignore_newlines: true,
 2883                ignore_brackets: false,
 2884            },
 2885            window,
 2886            cx,
 2887        );
 2888    });
 2889    cx.assert_editor_state(r#"ˇ"#);
 2890
 2891    cx.update_editor(|editor, window, cx| {
 2892        editor.delete_to_next_word_end(
 2893            &DeleteToNextWordEnd {
 2894                ignore_newlines: true,
 2895                ignore_brackets: false,
 2896            },
 2897            window,
 2898            cx,
 2899        );
 2900    });
 2901    cx.assert_editor_state(r#"ˇ"#);
 2902
 2903    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 2904    cx.update_editor(|editor, window, cx| {
 2905        editor.delete_to_previous_word_start(
 2906            &DeleteToPreviousWordStart {
 2907                ignore_newlines: true,
 2908                ignore_brackets: true,
 2909            },
 2910            window,
 2911            cx,
 2912        );
 2913    });
 2914    cx.assert_editor_state(r#"macroˇCOMMENT");"#);
 2915}
 2916
 2917#[gpui::test]
 2918fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 2919    init_test(cx, |_| {});
 2920
 2921    let editor = cx.add_window(|window, cx| {
 2922        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 2923        build_editor(buffer, window, cx)
 2924    });
 2925    let del_to_prev_word_start = DeleteToPreviousWordStart {
 2926        ignore_newlines: false,
 2927        ignore_brackets: false,
 2928    };
 2929    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 2930        ignore_newlines: true,
 2931        ignore_brackets: false,
 2932    };
 2933
 2934    _ = editor.update(cx, |editor, window, cx| {
 2935        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2936            s.select_display_ranges([
 2937                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 2938            ])
 2939        });
 2940        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2941        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 2942        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2943        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
 2944        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2945        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
 2946        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2947        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
 2948        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2949        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 2950        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2951        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2952    });
 2953}
 2954
 2955#[gpui::test]
 2956fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 2957    init_test(cx, |_| {});
 2958
 2959    let editor = cx.add_window(|window, cx| {
 2960        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 2961        build_editor(buffer, window, cx)
 2962    });
 2963    let del_to_next_word_end = DeleteToNextWordEnd {
 2964        ignore_newlines: false,
 2965        ignore_brackets: false,
 2966    };
 2967    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 2968        ignore_newlines: true,
 2969        ignore_brackets: false,
 2970    };
 2971
 2972    _ = editor.update(cx, |editor, window, cx| {
 2973        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2974            s.select_display_ranges([
 2975                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 2976            ])
 2977        });
 2978        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2979        assert_eq!(
 2980            editor.buffer.read(cx).read(cx).text(),
 2981            "one\n   two\nthree\n   four"
 2982        );
 2983        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2984        assert_eq!(
 2985            editor.buffer.read(cx).read(cx).text(),
 2986            "\n   two\nthree\n   four"
 2987        );
 2988        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2989        assert_eq!(
 2990            editor.buffer.read(cx).read(cx).text(),
 2991            "two\nthree\n   four"
 2992        );
 2993        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2994        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 2995        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2996        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   four");
 2997        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2998        assert_eq!(editor.buffer.read(cx).read(cx).text(), "four");
 2999        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3000        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3001    });
 3002}
 3003
 3004#[gpui::test]
 3005fn test_newline(cx: &mut TestAppContext) {
 3006    init_test(cx, |_| {});
 3007
 3008    let editor = cx.add_window(|window, cx| {
 3009        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 3010        build_editor(buffer, window, cx)
 3011    });
 3012
 3013    _ = editor.update(cx, |editor, window, cx| {
 3014        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3015            s.select_display_ranges([
 3016                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 3017                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 3018                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 3019            ])
 3020        });
 3021
 3022        editor.newline(&Newline, window, cx);
 3023        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 3024    });
 3025}
 3026
 3027#[gpui::test]
 3028fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 3029    init_test(cx, |_| {});
 3030
 3031    let editor = cx.add_window(|window, cx| {
 3032        let buffer = MultiBuffer::build_simple(
 3033            "
 3034                a
 3035                b(
 3036                    X
 3037                )
 3038                c(
 3039                    X
 3040                )
 3041            "
 3042            .unindent()
 3043            .as_str(),
 3044            cx,
 3045        );
 3046        let mut editor = build_editor(buffer, window, cx);
 3047        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3048            s.select_ranges([
 3049                Point::new(2, 4)..Point::new(2, 5),
 3050                Point::new(5, 4)..Point::new(5, 5),
 3051            ])
 3052        });
 3053        editor
 3054    });
 3055
 3056    _ = editor.update(cx, |editor, window, cx| {
 3057        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3058        editor.buffer.update(cx, |buffer, cx| {
 3059            buffer.edit(
 3060                [
 3061                    (Point::new(1, 2)..Point::new(3, 0), ""),
 3062                    (Point::new(4, 2)..Point::new(6, 0), ""),
 3063                ],
 3064                None,
 3065                cx,
 3066            );
 3067            assert_eq!(
 3068                buffer.read(cx).text(),
 3069                "
 3070                    a
 3071                    b()
 3072                    c()
 3073                "
 3074                .unindent()
 3075            );
 3076        });
 3077        assert_eq!(
 3078            editor.selections.ranges(cx),
 3079            &[
 3080                Point::new(1, 2)..Point::new(1, 2),
 3081                Point::new(2, 2)..Point::new(2, 2),
 3082            ],
 3083        );
 3084
 3085        editor.newline(&Newline, window, cx);
 3086        assert_eq!(
 3087            editor.text(cx),
 3088            "
 3089                a
 3090                b(
 3091                )
 3092                c(
 3093                )
 3094            "
 3095            .unindent()
 3096        );
 3097
 3098        // The selections are moved after the inserted newlines
 3099        assert_eq!(
 3100            editor.selections.ranges(cx),
 3101            &[
 3102                Point::new(2, 0)..Point::new(2, 0),
 3103                Point::new(4, 0)..Point::new(4, 0),
 3104            ],
 3105        );
 3106    });
 3107}
 3108
 3109#[gpui::test]
 3110async fn test_newline_above(cx: &mut TestAppContext) {
 3111    init_test(cx, |settings| {
 3112        settings.defaults.tab_size = NonZeroU32::new(4)
 3113    });
 3114
 3115    let language = Arc::new(
 3116        Language::new(
 3117            LanguageConfig::default(),
 3118            Some(tree_sitter_rust::LANGUAGE.into()),
 3119        )
 3120        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3121        .unwrap(),
 3122    );
 3123
 3124    let mut cx = EditorTestContext::new(cx).await;
 3125    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3126    cx.set_state(indoc! {"
 3127        const a: ˇA = (
 3128 3129                «const_functionˇ»(ˇ),
 3130                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3131 3132        ˇ);ˇ
 3133    "});
 3134
 3135    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 3136    cx.assert_editor_state(indoc! {"
 3137        ˇ
 3138        const a: A = (
 3139            ˇ
 3140            (
 3141                ˇ
 3142                ˇ
 3143                const_function(),
 3144                ˇ
 3145                ˇ
 3146                ˇ
 3147                ˇ
 3148                something_else,
 3149                ˇ
 3150            )
 3151            ˇ
 3152            ˇ
 3153        );
 3154    "});
 3155}
 3156
 3157#[gpui::test]
 3158async fn test_newline_below(cx: &mut TestAppContext) {
 3159    init_test(cx, |settings| {
 3160        settings.defaults.tab_size = NonZeroU32::new(4)
 3161    });
 3162
 3163    let language = Arc::new(
 3164        Language::new(
 3165            LanguageConfig::default(),
 3166            Some(tree_sitter_rust::LANGUAGE.into()),
 3167        )
 3168        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3169        .unwrap(),
 3170    );
 3171
 3172    let mut cx = EditorTestContext::new(cx).await;
 3173    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3174    cx.set_state(indoc! {"
 3175        const a: ˇA = (
 3176 3177                «const_functionˇ»(ˇ),
 3178                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3179 3180        ˇ);ˇ
 3181    "});
 3182
 3183    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 3184    cx.assert_editor_state(indoc! {"
 3185        const a: A = (
 3186            ˇ
 3187            (
 3188                ˇ
 3189                const_function(),
 3190                ˇ
 3191                ˇ
 3192                something_else,
 3193                ˇ
 3194                ˇ
 3195                ˇ
 3196                ˇ
 3197            )
 3198            ˇ
 3199        );
 3200        ˇ
 3201        ˇ
 3202    "});
 3203}
 3204
 3205#[gpui::test]
 3206async fn test_newline_comments(cx: &mut TestAppContext) {
 3207    init_test(cx, |settings| {
 3208        settings.defaults.tab_size = NonZeroU32::new(4)
 3209    });
 3210
 3211    let language = Arc::new(Language::new(
 3212        LanguageConfig {
 3213            line_comments: vec!["// ".into()],
 3214            ..LanguageConfig::default()
 3215        },
 3216        None,
 3217    ));
 3218    {
 3219        let mut cx = EditorTestContext::new(cx).await;
 3220        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3221        cx.set_state(indoc! {"
 3222        // Fooˇ
 3223    "});
 3224
 3225        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3226        cx.assert_editor_state(indoc! {"
 3227        // Foo
 3228        // ˇ
 3229    "});
 3230        // Ensure that we add comment prefix when existing line contains space
 3231        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3232        cx.assert_editor_state(
 3233            indoc! {"
 3234        // Foo
 3235        //s
 3236        // ˇ
 3237    "}
 3238            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3239            .as_str(),
 3240        );
 3241        // Ensure that we add comment prefix when existing line does not contain space
 3242        cx.set_state(indoc! {"
 3243        // Foo
 3244        //ˇ
 3245    "});
 3246        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3247        cx.assert_editor_state(indoc! {"
 3248        // Foo
 3249        //
 3250        // ˇ
 3251    "});
 3252        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 3253        cx.set_state(indoc! {"
 3254        ˇ// Foo
 3255    "});
 3256        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3257        cx.assert_editor_state(indoc! {"
 3258
 3259        ˇ// Foo
 3260    "});
 3261    }
 3262    // Ensure that comment continuations can be disabled.
 3263    update_test_language_settings(cx, |settings| {
 3264        settings.defaults.extend_comment_on_newline = Some(false);
 3265    });
 3266    let mut cx = EditorTestContext::new(cx).await;
 3267    cx.set_state(indoc! {"
 3268        // Fooˇ
 3269    "});
 3270    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3271    cx.assert_editor_state(indoc! {"
 3272        // Foo
 3273        ˇ
 3274    "});
 3275}
 3276
 3277#[gpui::test]
 3278async fn test_newline_comments_with_multiple_delimiters(cx: &mut TestAppContext) {
 3279    init_test(cx, |settings| {
 3280        settings.defaults.tab_size = NonZeroU32::new(4)
 3281    });
 3282
 3283    let language = Arc::new(Language::new(
 3284        LanguageConfig {
 3285            line_comments: vec!["// ".into(), "/// ".into()],
 3286            ..LanguageConfig::default()
 3287        },
 3288        None,
 3289    ));
 3290    {
 3291        let mut cx = EditorTestContext::new(cx).await;
 3292        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3293        cx.set_state(indoc! {"
 3294        //ˇ
 3295    "});
 3296        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3297        cx.assert_editor_state(indoc! {"
 3298        //
 3299        // ˇ
 3300    "});
 3301
 3302        cx.set_state(indoc! {"
 3303        ///ˇ
 3304    "});
 3305        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3306        cx.assert_editor_state(indoc! {"
 3307        ///
 3308        /// ˇ
 3309    "});
 3310    }
 3311}
 3312
 3313#[gpui::test]
 3314async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 3315    init_test(cx, |settings| {
 3316        settings.defaults.tab_size = NonZeroU32::new(4)
 3317    });
 3318
 3319    let language = Arc::new(
 3320        Language::new(
 3321            LanguageConfig {
 3322                documentation_comment: Some(language::BlockCommentConfig {
 3323                    start: "/**".into(),
 3324                    end: "*/".into(),
 3325                    prefix: "* ".into(),
 3326                    tab_size: 1,
 3327                }),
 3328
 3329                ..LanguageConfig::default()
 3330            },
 3331            Some(tree_sitter_rust::LANGUAGE.into()),
 3332        )
 3333        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 3334        .unwrap(),
 3335    );
 3336
 3337    {
 3338        let mut cx = EditorTestContext::new(cx).await;
 3339        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3340        cx.set_state(indoc! {"
 3341        /**ˇ
 3342    "});
 3343
 3344        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3345        cx.assert_editor_state(indoc! {"
 3346        /**
 3347         * ˇ
 3348    "});
 3349        // Ensure that if cursor is before the comment start,
 3350        // we do not actually insert a comment prefix.
 3351        cx.set_state(indoc! {"
 3352        ˇ/**
 3353    "});
 3354        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3355        cx.assert_editor_state(indoc! {"
 3356
 3357        ˇ/**
 3358    "});
 3359        // Ensure that if cursor is between it doesn't add comment prefix.
 3360        cx.set_state(indoc! {"
 3361        /*ˇ*
 3362    "});
 3363        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3364        cx.assert_editor_state(indoc! {"
 3365        /*
 3366        ˇ*
 3367    "});
 3368        // Ensure that if suffix exists on same line after cursor it adds new line.
 3369        cx.set_state(indoc! {"
 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 if suffix exists on same line after cursor with space it adds new line.
 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    "});
 3388        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3389        cx.set_state(indoc! {"
 3390        /** ˇ*/
 3391    "});
 3392        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3393        cx.assert_editor_state(
 3394            indoc! {"
 3395        /**s
 3396         * ˇ
 3397         */
 3398    "}
 3399            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3400            .as_str(),
 3401        );
 3402        // Ensure that delimiter space is preserved when newline on already
 3403        // spaced delimiter.
 3404        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3405        cx.assert_editor_state(
 3406            indoc! {"
 3407        /**s
 3408         *s
 3409         * ˇ
 3410         */
 3411    "}
 3412            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3413            .as_str(),
 3414        );
 3415        // Ensure that delimiter space is preserved when space is not
 3416        // on existing delimiter.
 3417        cx.set_state(indoc! {"
 3418        /**
 3419 3420         */
 3421    "});
 3422        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3423        cx.assert_editor_state(indoc! {"
 3424        /**
 3425         *
 3426         * ˇ
 3427         */
 3428    "});
 3429        // Ensure that if suffix exists on same line after cursor it
 3430        // doesn't add extra new line if prefix is not on same line.
 3431        cx.set_state(indoc! {"
 3432        /**
 3433        ˇ*/
 3434    "});
 3435        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3436        cx.assert_editor_state(indoc! {"
 3437        /**
 3438
 3439        ˇ*/
 3440    "});
 3441        // Ensure that it detects suffix after existing prefix.
 3442        cx.set_state(indoc! {"
 3443        /**ˇ/
 3444    "});
 3445        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3446        cx.assert_editor_state(indoc! {"
 3447        /**
 3448        ˇ/
 3449    "});
 3450        // Ensure that if suffix exists on same line before
 3451        // cursor it does not add comment prefix.
 3452        cx.set_state(indoc! {"
 3453        /** */ˇ
 3454    "});
 3455        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3456        cx.assert_editor_state(indoc! {"
 3457        /** */
 3458        ˇ
 3459    "});
 3460        // Ensure that if suffix exists on same line before
 3461        // cursor it does not add comment prefix.
 3462        cx.set_state(indoc! {"
 3463        /**
 3464         *
 3465         */ˇ
 3466    "});
 3467        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3468        cx.assert_editor_state(indoc! {"
 3469        /**
 3470         *
 3471         */
 3472         ˇ
 3473    "});
 3474
 3475        // Ensure that inline comment followed by code
 3476        // doesn't add comment prefix on newline
 3477        cx.set_state(indoc! {"
 3478        /** */ textˇ
 3479    "});
 3480        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3481        cx.assert_editor_state(indoc! {"
 3482        /** */ text
 3483        ˇ
 3484    "});
 3485
 3486        // Ensure that text after comment end tag
 3487        // doesn't add comment prefix on newline
 3488        cx.set_state(indoc! {"
 3489        /**
 3490         *
 3491         */ˇtext
 3492    "});
 3493        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3494        cx.assert_editor_state(indoc! {"
 3495        /**
 3496         *
 3497         */
 3498         ˇtext
 3499    "});
 3500
 3501        // Ensure if not comment block it doesn't
 3502        // add comment prefix on newline
 3503        cx.set_state(indoc! {"
 3504        * textˇ
 3505    "});
 3506        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3507        cx.assert_editor_state(indoc! {"
 3508        * text
 3509        ˇ
 3510    "});
 3511    }
 3512    // Ensure that comment continuations can be disabled.
 3513    update_test_language_settings(cx, |settings| {
 3514        settings.defaults.extend_comment_on_newline = Some(false);
 3515    });
 3516    let mut cx = EditorTestContext::new(cx).await;
 3517    cx.set_state(indoc! {"
 3518        /**ˇ
 3519    "});
 3520    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3521    cx.assert_editor_state(indoc! {"
 3522        /**
 3523        ˇ
 3524    "});
 3525}
 3526
 3527#[gpui::test]
 3528async fn test_newline_comments_with_block_comment(cx: &mut TestAppContext) {
 3529    init_test(cx, |settings| {
 3530        settings.defaults.tab_size = NonZeroU32::new(4)
 3531    });
 3532
 3533    let lua_language = Arc::new(Language::new(
 3534        LanguageConfig {
 3535            line_comments: vec!["--".into()],
 3536            block_comment: Some(language::BlockCommentConfig {
 3537                start: "--[[".into(),
 3538                prefix: "".into(),
 3539                end: "]]".into(),
 3540                tab_size: 0,
 3541            }),
 3542            ..LanguageConfig::default()
 3543        },
 3544        None,
 3545    ));
 3546
 3547    let mut cx = EditorTestContext::new(cx).await;
 3548    cx.update_buffer(|buffer, cx| buffer.set_language(Some(lua_language), cx));
 3549
 3550    // Line with line comment should extend
 3551    cx.set_state(indoc! {"
 3552        --ˇ
 3553    "});
 3554    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3555    cx.assert_editor_state(indoc! {"
 3556        --
 3557        --ˇ
 3558    "});
 3559
 3560    // Line with block comment that matches line comment should not extend
 3561    cx.set_state(indoc! {"
 3562        --[[ˇ
 3563    "});
 3564    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3565    cx.assert_editor_state(indoc! {"
 3566        --[[
 3567        ˇ
 3568    "});
 3569}
 3570
 3571#[gpui::test]
 3572fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 3573    init_test(cx, |_| {});
 3574
 3575    let editor = cx.add_window(|window, cx| {
 3576        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 3577        let mut editor = build_editor(buffer, window, cx);
 3578        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3579            s.select_ranges([3..4, 11..12, 19..20])
 3580        });
 3581        editor
 3582    });
 3583
 3584    _ = editor.update(cx, |editor, window, cx| {
 3585        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3586        editor.buffer.update(cx, |buffer, cx| {
 3587            buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
 3588            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 3589        });
 3590        assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
 3591
 3592        editor.insert("Z", window, cx);
 3593        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 3594
 3595        // The selections are moved after the inserted characters
 3596        assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
 3597    });
 3598}
 3599
 3600#[gpui::test]
 3601async fn test_tab(cx: &mut TestAppContext) {
 3602    init_test(cx, |settings| {
 3603        settings.defaults.tab_size = NonZeroU32::new(3)
 3604    });
 3605
 3606    let mut cx = EditorTestContext::new(cx).await;
 3607    cx.set_state(indoc! {"
 3608        ˇabˇc
 3609        ˇ🏀ˇ🏀ˇefg
 3610 3611    "});
 3612    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3613    cx.assert_editor_state(indoc! {"
 3614           ˇab ˇc
 3615           ˇ🏀  ˇ🏀  ˇefg
 3616        d  ˇ
 3617    "});
 3618
 3619    cx.set_state(indoc! {"
 3620        a
 3621        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3622    "});
 3623    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3624    cx.assert_editor_state(indoc! {"
 3625        a
 3626           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3627    "});
 3628}
 3629
 3630#[gpui::test]
 3631async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3632    init_test(cx, |_| {});
 3633
 3634    let mut cx = EditorTestContext::new(cx).await;
 3635    let language = Arc::new(
 3636        Language::new(
 3637            LanguageConfig::default(),
 3638            Some(tree_sitter_rust::LANGUAGE.into()),
 3639        )
 3640        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3641        .unwrap(),
 3642    );
 3643    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3644
 3645    // test when all cursors are not at suggested indent
 3646    // then simply move to their suggested indent location
 3647    cx.set_state(indoc! {"
 3648        const a: B = (
 3649            c(
 3650        ˇ
 3651        ˇ    )
 3652        );
 3653    "});
 3654    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3655    cx.assert_editor_state(indoc! {"
 3656        const a: B = (
 3657            c(
 3658                ˇ
 3659            ˇ)
 3660        );
 3661    "});
 3662
 3663    // test cursor already at suggested indent not moving when
 3664    // other cursors are yet to reach their suggested indents
 3665    cx.set_state(indoc! {"
 3666        ˇ
 3667        const a: B = (
 3668            c(
 3669                d(
 3670        ˇ
 3671                )
 3672        ˇ
 3673        ˇ    )
 3674        );
 3675    "});
 3676    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3677    cx.assert_editor_state(indoc! {"
 3678        ˇ
 3679        const a: B = (
 3680            c(
 3681                d(
 3682                    ˇ
 3683                )
 3684                ˇ
 3685            ˇ)
 3686        );
 3687    "});
 3688    // test when all cursors are at suggested indent then tab is inserted
 3689    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3690    cx.assert_editor_state(indoc! {"
 3691            ˇ
 3692        const a: B = (
 3693            c(
 3694                d(
 3695                        ˇ
 3696                )
 3697                    ˇ
 3698                ˇ)
 3699        );
 3700    "});
 3701
 3702    // test when current indent is less than suggested indent,
 3703    // we adjust line to match suggested indent and move cursor to it
 3704    //
 3705    // when no other cursor is at word boundary, all of them should move
 3706    cx.set_state(indoc! {"
 3707        const a: B = (
 3708            c(
 3709                d(
 3710        ˇ
 3711        ˇ   )
 3712        ˇ   )
 3713        );
 3714    "});
 3715    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3716    cx.assert_editor_state(indoc! {"
 3717        const a: B = (
 3718            c(
 3719                d(
 3720                    ˇ
 3721                ˇ)
 3722            ˇ)
 3723        );
 3724    "});
 3725
 3726    // test when current indent is less than suggested indent,
 3727    // we adjust line to match suggested indent and move cursor to it
 3728    //
 3729    // when some other cursor is at word boundary, it should not move
 3730    cx.set_state(indoc! {"
 3731        const a: B = (
 3732            c(
 3733                d(
 3734        ˇ
 3735        ˇ   )
 3736           ˇ)
 3737        );
 3738    "});
 3739    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3740    cx.assert_editor_state(indoc! {"
 3741        const a: B = (
 3742            c(
 3743                d(
 3744                    ˇ
 3745                ˇ)
 3746            ˇ)
 3747        );
 3748    "});
 3749
 3750    // test when current indent is more than suggested indent,
 3751    // we just move cursor to current indent instead of suggested indent
 3752    //
 3753    // when no other cursor is at word boundary, all of them should move
 3754    cx.set_state(indoc! {"
 3755        const a: B = (
 3756            c(
 3757                d(
 3758        ˇ
 3759        ˇ                )
 3760        ˇ   )
 3761        );
 3762    "});
 3763    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3764    cx.assert_editor_state(indoc! {"
 3765        const a: B = (
 3766            c(
 3767                d(
 3768                    ˇ
 3769                        ˇ)
 3770            ˇ)
 3771        );
 3772    "});
 3773    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3774    cx.assert_editor_state(indoc! {"
 3775        const a: B = (
 3776            c(
 3777                d(
 3778                        ˇ
 3779                            ˇ)
 3780                ˇ)
 3781        );
 3782    "});
 3783
 3784    // test when current indent is more than suggested indent,
 3785    // we just move cursor to current indent instead of suggested indent
 3786    //
 3787    // when some other cursor is at word boundary, it doesn't move
 3788    cx.set_state(indoc! {"
 3789        const a: B = (
 3790            c(
 3791                d(
 3792        ˇ
 3793        ˇ                )
 3794            ˇ)
 3795        );
 3796    "});
 3797    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3798    cx.assert_editor_state(indoc! {"
 3799        const a: B = (
 3800            c(
 3801                d(
 3802                    ˇ
 3803                        ˇ)
 3804            ˇ)
 3805        );
 3806    "});
 3807
 3808    // handle auto-indent when there are multiple cursors on the same line
 3809    cx.set_state(indoc! {"
 3810        const a: B = (
 3811            c(
 3812        ˇ    ˇ
 3813        ˇ    )
 3814        );
 3815    "});
 3816    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3817    cx.assert_editor_state(indoc! {"
 3818        const a: B = (
 3819            c(
 3820                ˇ
 3821            ˇ)
 3822        );
 3823    "});
 3824}
 3825
 3826#[gpui::test]
 3827async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 3828    init_test(cx, |settings| {
 3829        settings.defaults.tab_size = NonZeroU32::new(3)
 3830    });
 3831
 3832    let mut cx = EditorTestContext::new(cx).await;
 3833    cx.set_state(indoc! {"
 3834         ˇ
 3835        \t ˇ
 3836        \t  ˇ
 3837        \t   ˇ
 3838         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 3839    "});
 3840
 3841    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3842    cx.assert_editor_state(indoc! {"
 3843           ˇ
 3844        \t   ˇ
 3845        \t   ˇ
 3846        \t      ˇ
 3847         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 3848    "});
 3849}
 3850
 3851#[gpui::test]
 3852async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 3853    init_test(cx, |settings| {
 3854        settings.defaults.tab_size = NonZeroU32::new(4)
 3855    });
 3856
 3857    let language = Arc::new(
 3858        Language::new(
 3859            LanguageConfig::default(),
 3860            Some(tree_sitter_rust::LANGUAGE.into()),
 3861        )
 3862        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 3863        .unwrap(),
 3864    );
 3865
 3866    let mut cx = EditorTestContext::new(cx).await;
 3867    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3868    cx.set_state(indoc! {"
 3869        fn a() {
 3870            if b {
 3871        \t ˇc
 3872            }
 3873        }
 3874    "});
 3875
 3876    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3877    cx.assert_editor_state(indoc! {"
 3878        fn a() {
 3879            if b {
 3880                ˇc
 3881            }
 3882        }
 3883    "});
 3884}
 3885
 3886#[gpui::test]
 3887async fn test_indent_outdent(cx: &mut TestAppContext) {
 3888    init_test(cx, |settings| {
 3889        settings.defaults.tab_size = NonZeroU32::new(4);
 3890    });
 3891
 3892    let mut cx = EditorTestContext::new(cx).await;
 3893
 3894    cx.set_state(indoc! {"
 3895          «oneˇ» «twoˇ»
 3896        three
 3897         four
 3898    "});
 3899    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3900    cx.assert_editor_state(indoc! {"
 3901            «oneˇ» «twoˇ»
 3902        three
 3903         four
 3904    "});
 3905
 3906    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3907    cx.assert_editor_state(indoc! {"
 3908        «oneˇ» «twoˇ»
 3909        three
 3910         four
 3911    "});
 3912
 3913    // select across line ending
 3914    cx.set_state(indoc! {"
 3915        one two
 3916        t«hree
 3917        ˇ» four
 3918    "});
 3919    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3920    cx.assert_editor_state(indoc! {"
 3921        one two
 3922            t«hree
 3923        ˇ» four
 3924    "});
 3925
 3926    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3927    cx.assert_editor_state(indoc! {"
 3928        one two
 3929        t«hree
 3930        ˇ» four
 3931    "});
 3932
 3933    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3934    cx.set_state(indoc! {"
 3935        one two
 3936        ˇthree
 3937            four
 3938    "});
 3939    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3940    cx.assert_editor_state(indoc! {"
 3941        one two
 3942            ˇthree
 3943            four
 3944    "});
 3945
 3946    cx.set_state(indoc! {"
 3947        one two
 3948        ˇ    three
 3949            four
 3950    "});
 3951    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3952    cx.assert_editor_state(indoc! {"
 3953        one two
 3954        ˇthree
 3955            four
 3956    "});
 3957}
 3958
 3959#[gpui::test]
 3960async fn test_indent_yaml_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 3961    // This is a regression test for issue #33761
 3962    init_test(cx, |_| {});
 3963
 3964    let mut cx = EditorTestContext::new(cx).await;
 3965    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3966    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3967
 3968    cx.set_state(
 3969        r#"ˇ#     ingress:
 3970ˇ#         api:
 3971ˇ#             enabled: false
 3972ˇ#             pathType: Prefix
 3973ˇ#           console:
 3974ˇ#               enabled: false
 3975ˇ#               pathType: Prefix
 3976"#,
 3977    );
 3978
 3979    // Press tab to indent all lines
 3980    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3981
 3982    cx.assert_editor_state(
 3983        r#"    ˇ#     ingress:
 3984    ˇ#         api:
 3985    ˇ#             enabled: false
 3986    ˇ#             pathType: Prefix
 3987    ˇ#           console:
 3988    ˇ#               enabled: false
 3989    ˇ#               pathType: Prefix
 3990"#,
 3991    );
 3992}
 3993
 3994#[gpui::test]
 3995async fn test_indent_yaml_non_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 3996    // This is a test to make sure our fix for issue #33761 didn't break anything
 3997    init_test(cx, |_| {});
 3998
 3999    let mut cx = EditorTestContext::new(cx).await;
 4000    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 4001    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 4002
 4003    cx.set_state(
 4004        r#"ˇingress:
 4005ˇ  api:
 4006ˇ    enabled: false
 4007ˇ    pathType: Prefix
 4008"#,
 4009    );
 4010
 4011    // Press tab to indent all lines
 4012    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4013
 4014    cx.assert_editor_state(
 4015        r#"ˇingress:
 4016    ˇapi:
 4017        ˇenabled: false
 4018        ˇpathType: Prefix
 4019"#,
 4020    );
 4021}
 4022
 4023#[gpui::test]
 4024async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 4025    init_test(cx, |settings| {
 4026        settings.defaults.hard_tabs = Some(true);
 4027    });
 4028
 4029    let mut cx = EditorTestContext::new(cx).await;
 4030
 4031    // select two ranges on one line
 4032    cx.set_state(indoc! {"
 4033        «oneˇ» «twoˇ»
 4034        three
 4035        four
 4036    "});
 4037    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4038    cx.assert_editor_state(indoc! {"
 4039        \t«oneˇ» «twoˇ»
 4040        three
 4041        four
 4042    "});
 4043    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4044    cx.assert_editor_state(indoc! {"
 4045        \t\t«oneˇ» «twoˇ»
 4046        three
 4047        four
 4048    "});
 4049    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4050    cx.assert_editor_state(indoc! {"
 4051        \t«oneˇ» «twoˇ»
 4052        three
 4053        four
 4054    "});
 4055    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4056    cx.assert_editor_state(indoc! {"
 4057        «oneˇ» «twoˇ»
 4058        three
 4059        four
 4060    "});
 4061
 4062    // select across a line ending
 4063    cx.set_state(indoc! {"
 4064        one two
 4065        t«hree
 4066        ˇ»four
 4067    "});
 4068    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4069    cx.assert_editor_state(indoc! {"
 4070        one two
 4071        \tt«hree
 4072        ˇ»four
 4073    "});
 4074    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4075    cx.assert_editor_state(indoc! {"
 4076        one two
 4077        \t\tt«hree
 4078        ˇ»four
 4079    "});
 4080    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4081    cx.assert_editor_state(indoc! {"
 4082        one two
 4083        \tt«hree
 4084        ˇ»four
 4085    "});
 4086    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4087    cx.assert_editor_state(indoc! {"
 4088        one two
 4089        t«hree
 4090        ˇ»four
 4091    "});
 4092
 4093    // Ensure that indenting/outdenting works when the cursor is at column 0.
 4094    cx.set_state(indoc! {"
 4095        one two
 4096        ˇthree
 4097        four
 4098    "});
 4099    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4100    cx.assert_editor_state(indoc! {"
 4101        one two
 4102        ˇthree
 4103        four
 4104    "});
 4105    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4106    cx.assert_editor_state(indoc! {"
 4107        one two
 4108        \tˇthree
 4109        four
 4110    "});
 4111    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4112    cx.assert_editor_state(indoc! {"
 4113        one two
 4114        ˇthree
 4115        four
 4116    "});
 4117}
 4118
 4119#[gpui::test]
 4120fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 4121    init_test(cx, |settings| {
 4122        settings.languages.0.extend([
 4123            (
 4124                "TOML".into(),
 4125                LanguageSettingsContent {
 4126                    tab_size: NonZeroU32::new(2),
 4127                    ..Default::default()
 4128                },
 4129            ),
 4130            (
 4131                "Rust".into(),
 4132                LanguageSettingsContent {
 4133                    tab_size: NonZeroU32::new(4),
 4134                    ..Default::default()
 4135                },
 4136            ),
 4137        ]);
 4138    });
 4139
 4140    let toml_language = Arc::new(Language::new(
 4141        LanguageConfig {
 4142            name: "TOML".into(),
 4143            ..Default::default()
 4144        },
 4145        None,
 4146    ));
 4147    let rust_language = Arc::new(Language::new(
 4148        LanguageConfig {
 4149            name: "Rust".into(),
 4150            ..Default::default()
 4151        },
 4152        None,
 4153    ));
 4154
 4155    let toml_buffer =
 4156        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 4157    let rust_buffer =
 4158        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 4159    let multibuffer = cx.new(|cx| {
 4160        let mut multibuffer = MultiBuffer::new(ReadWrite);
 4161        multibuffer.push_excerpts(
 4162            toml_buffer.clone(),
 4163            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 4164            cx,
 4165        );
 4166        multibuffer.push_excerpts(
 4167            rust_buffer.clone(),
 4168            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 4169            cx,
 4170        );
 4171        multibuffer
 4172    });
 4173
 4174    cx.add_window(|window, cx| {
 4175        let mut editor = build_editor(multibuffer, window, cx);
 4176
 4177        assert_eq!(
 4178            editor.text(cx),
 4179            indoc! {"
 4180                a = 1
 4181                b = 2
 4182
 4183                const c: usize = 3;
 4184            "}
 4185        );
 4186
 4187        select_ranges(
 4188            &mut editor,
 4189            indoc! {"
 4190                «aˇ» = 1
 4191                b = 2
 4192
 4193                «const c:ˇ» usize = 3;
 4194            "},
 4195            window,
 4196            cx,
 4197        );
 4198
 4199        editor.tab(&Tab, window, cx);
 4200        assert_text_with_selections(
 4201            &mut editor,
 4202            indoc! {"
 4203                  «aˇ» = 1
 4204                b = 2
 4205
 4206                    «const c:ˇ» usize = 3;
 4207            "},
 4208            cx,
 4209        );
 4210        editor.backtab(&Backtab, window, cx);
 4211        assert_text_with_selections(
 4212            &mut editor,
 4213            indoc! {"
 4214                «aˇ» = 1
 4215                b = 2
 4216
 4217                «const c:ˇ» usize = 3;
 4218            "},
 4219            cx,
 4220        );
 4221
 4222        editor
 4223    });
 4224}
 4225
 4226#[gpui::test]
 4227async fn test_backspace(cx: &mut TestAppContext) {
 4228    init_test(cx, |_| {});
 4229
 4230    let mut cx = EditorTestContext::new(cx).await;
 4231
 4232    // Basic backspace
 4233    cx.set_state(indoc! {"
 4234        onˇe two three
 4235        fou«rˇ» five six
 4236        seven «ˇeight nine
 4237        »ten
 4238    "});
 4239    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4240    cx.assert_editor_state(indoc! {"
 4241        oˇe two three
 4242        fouˇ five six
 4243        seven ˇten
 4244    "});
 4245
 4246    // Test backspace inside and around indents
 4247    cx.set_state(indoc! {"
 4248        zero
 4249            ˇone
 4250                ˇtwo
 4251            ˇ ˇ ˇ  three
 4252        ˇ  ˇ  four
 4253    "});
 4254    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4255    cx.assert_editor_state(indoc! {"
 4256        zero
 4257        ˇone
 4258            ˇtwo
 4259        ˇ  threeˇ  four
 4260    "});
 4261}
 4262
 4263#[gpui::test]
 4264async fn test_delete(cx: &mut TestAppContext) {
 4265    init_test(cx, |_| {});
 4266
 4267    let mut cx = EditorTestContext::new(cx).await;
 4268    cx.set_state(indoc! {"
 4269        onˇe two three
 4270        fou«rˇ» five six
 4271        seven «ˇeight nine
 4272        »ten
 4273    "});
 4274    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 4275    cx.assert_editor_state(indoc! {"
 4276        onˇ two three
 4277        fouˇ five six
 4278        seven ˇten
 4279    "});
 4280}
 4281
 4282#[gpui::test]
 4283fn test_delete_line(cx: &mut TestAppContext) {
 4284    init_test(cx, |_| {});
 4285
 4286    let editor = cx.add_window(|window, cx| {
 4287        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4288        build_editor(buffer, window, cx)
 4289    });
 4290    _ = editor.update(cx, |editor, window, cx| {
 4291        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4292            s.select_display_ranges([
 4293                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4294                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4295                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4296            ])
 4297        });
 4298        editor.delete_line(&DeleteLine, window, cx);
 4299        assert_eq!(editor.display_text(cx), "ghi");
 4300        assert_eq!(
 4301            editor.selections.display_ranges(cx),
 4302            vec![
 4303                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4304                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 4305            ]
 4306        );
 4307    });
 4308
 4309    let editor = cx.add_window(|window, cx| {
 4310        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4311        build_editor(buffer, window, cx)
 4312    });
 4313    _ = editor.update(cx, |editor, window, cx| {
 4314        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4315            s.select_display_ranges([
 4316                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 4317            ])
 4318        });
 4319        editor.delete_line(&DeleteLine, window, cx);
 4320        assert_eq!(editor.display_text(cx), "ghi\n");
 4321        assert_eq!(
 4322            editor.selections.display_ranges(cx),
 4323            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 4324        );
 4325    });
 4326}
 4327
 4328#[gpui::test]
 4329fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 4330    init_test(cx, |_| {});
 4331
 4332    cx.add_window(|window, cx| {
 4333        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4334        let mut editor = build_editor(buffer.clone(), window, cx);
 4335        let buffer = buffer.read(cx).as_singleton().unwrap();
 4336
 4337        assert_eq!(
 4338            editor.selections.ranges::<Point>(cx),
 4339            &[Point::new(0, 0)..Point::new(0, 0)]
 4340        );
 4341
 4342        // When on single line, replace newline at end by space
 4343        editor.join_lines(&JoinLines, window, cx);
 4344        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4345        assert_eq!(
 4346            editor.selections.ranges::<Point>(cx),
 4347            &[Point::new(0, 3)..Point::new(0, 3)]
 4348        );
 4349
 4350        // When multiple lines are selected, remove newlines that are spanned by the selection
 4351        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4352            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 4353        });
 4354        editor.join_lines(&JoinLines, window, cx);
 4355        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 4356        assert_eq!(
 4357            editor.selections.ranges::<Point>(cx),
 4358            &[Point::new(0, 11)..Point::new(0, 11)]
 4359        );
 4360
 4361        // Undo should be transactional
 4362        editor.undo(&Undo, window, cx);
 4363        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4364        assert_eq!(
 4365            editor.selections.ranges::<Point>(cx),
 4366            &[Point::new(0, 5)..Point::new(2, 2)]
 4367        );
 4368
 4369        // When joining an empty line don't insert a space
 4370        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4371            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 4372        });
 4373        editor.join_lines(&JoinLines, window, cx);
 4374        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 4375        assert_eq!(
 4376            editor.selections.ranges::<Point>(cx),
 4377            [Point::new(2, 3)..Point::new(2, 3)]
 4378        );
 4379
 4380        // We can remove trailing newlines
 4381        editor.join_lines(&JoinLines, window, cx);
 4382        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4383        assert_eq!(
 4384            editor.selections.ranges::<Point>(cx),
 4385            [Point::new(2, 3)..Point::new(2, 3)]
 4386        );
 4387
 4388        // We don't blow up on the last line
 4389        editor.join_lines(&JoinLines, window, cx);
 4390        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4391        assert_eq!(
 4392            editor.selections.ranges::<Point>(cx),
 4393            [Point::new(2, 3)..Point::new(2, 3)]
 4394        );
 4395
 4396        // reset to test indentation
 4397        editor.buffer.update(cx, |buffer, cx| {
 4398            buffer.edit(
 4399                [
 4400                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 4401                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 4402                ],
 4403                None,
 4404                cx,
 4405            )
 4406        });
 4407
 4408        // We remove any leading spaces
 4409        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 4410        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4411            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 4412        });
 4413        editor.join_lines(&JoinLines, window, cx);
 4414        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 4415
 4416        // We don't insert a space for a line containing only spaces
 4417        editor.join_lines(&JoinLines, window, cx);
 4418        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 4419
 4420        // We ignore any leading tabs
 4421        editor.join_lines(&JoinLines, window, cx);
 4422        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 4423
 4424        editor
 4425    });
 4426}
 4427
 4428#[gpui::test]
 4429fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 4430    init_test(cx, |_| {});
 4431
 4432    cx.add_window(|window, cx| {
 4433        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4434        let mut editor = build_editor(buffer.clone(), window, cx);
 4435        let buffer = buffer.read(cx).as_singleton().unwrap();
 4436
 4437        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4438            s.select_ranges([
 4439                Point::new(0, 2)..Point::new(1, 1),
 4440                Point::new(1, 2)..Point::new(1, 2),
 4441                Point::new(3, 1)..Point::new(3, 2),
 4442            ])
 4443        });
 4444
 4445        editor.join_lines(&JoinLines, window, cx);
 4446        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 4447
 4448        assert_eq!(
 4449            editor.selections.ranges::<Point>(cx),
 4450            [
 4451                Point::new(0, 7)..Point::new(0, 7),
 4452                Point::new(1, 3)..Point::new(1, 3)
 4453            ]
 4454        );
 4455        editor
 4456    });
 4457}
 4458
 4459#[gpui::test]
 4460async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 4461    init_test(cx, |_| {});
 4462
 4463    let mut cx = EditorTestContext::new(cx).await;
 4464
 4465    let diff_base = r#"
 4466        Line 0
 4467        Line 1
 4468        Line 2
 4469        Line 3
 4470        "#
 4471    .unindent();
 4472
 4473    cx.set_state(
 4474        &r#"
 4475        ˇLine 0
 4476        Line 1
 4477        Line 2
 4478        Line 3
 4479        "#
 4480        .unindent(),
 4481    );
 4482
 4483    cx.set_head_text(&diff_base);
 4484    executor.run_until_parked();
 4485
 4486    // Join lines
 4487    cx.update_editor(|editor, window, cx| {
 4488        editor.join_lines(&JoinLines, window, cx);
 4489    });
 4490    executor.run_until_parked();
 4491
 4492    cx.assert_editor_state(
 4493        &r#"
 4494        Line 0ˇ Line 1
 4495        Line 2
 4496        Line 3
 4497        "#
 4498        .unindent(),
 4499    );
 4500    // Join again
 4501    cx.update_editor(|editor, window, cx| {
 4502        editor.join_lines(&JoinLines, window, cx);
 4503    });
 4504    executor.run_until_parked();
 4505
 4506    cx.assert_editor_state(
 4507        &r#"
 4508        Line 0 Line 1ˇ Line 2
 4509        Line 3
 4510        "#
 4511        .unindent(),
 4512    );
 4513}
 4514
 4515#[gpui::test]
 4516async fn test_custom_newlines_cause_no_false_positive_diffs(
 4517    executor: BackgroundExecutor,
 4518    cx: &mut TestAppContext,
 4519) {
 4520    init_test(cx, |_| {});
 4521    let mut cx = EditorTestContext::new(cx).await;
 4522    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 4523    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 4524    executor.run_until_parked();
 4525
 4526    cx.update_editor(|editor, window, cx| {
 4527        let snapshot = editor.snapshot(window, cx);
 4528        assert_eq!(
 4529            snapshot
 4530                .buffer_snapshot()
 4531                .diff_hunks_in_range(0..snapshot.buffer_snapshot().len())
 4532                .collect::<Vec<_>>(),
 4533            Vec::new(),
 4534            "Should not have any diffs for files with custom newlines"
 4535        );
 4536    });
 4537}
 4538
 4539#[gpui::test]
 4540async fn test_manipulate_immutable_lines_with_single_selection(cx: &mut TestAppContext) {
 4541    init_test(cx, |_| {});
 4542
 4543    let mut cx = EditorTestContext::new(cx).await;
 4544
 4545    // Test sort_lines_case_insensitive()
 4546    cx.set_state(indoc! {"
 4547        «z
 4548        y
 4549        x
 4550        Z
 4551        Y
 4552        Xˇ»
 4553    "});
 4554    cx.update_editor(|e, window, cx| {
 4555        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 4556    });
 4557    cx.assert_editor_state(indoc! {"
 4558        «x
 4559        X
 4560        y
 4561        Y
 4562        z
 4563        Zˇ»
 4564    "});
 4565
 4566    // Test sort_lines_by_length()
 4567    //
 4568    // Demonstrates:
 4569    // - ∞ is 3 bytes UTF-8, but sorted by its char count (1)
 4570    // - sort is stable
 4571    cx.set_state(indoc! {"
 4572        «123
 4573        æ
 4574        12
 4575 4576        1
 4577        æˇ»
 4578    "});
 4579    cx.update_editor(|e, window, cx| e.sort_lines_by_length(&SortLinesByLength, window, cx));
 4580    cx.assert_editor_state(indoc! {"
 4581        «æ
 4582 4583        1
 4584        æ
 4585        12
 4586        123ˇ»
 4587    "});
 4588
 4589    // Test reverse_lines()
 4590    cx.set_state(indoc! {"
 4591        «5
 4592        4
 4593        3
 4594        2
 4595        1ˇ»
 4596    "});
 4597    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4598    cx.assert_editor_state(indoc! {"
 4599        «1
 4600        2
 4601        3
 4602        4
 4603        5ˇ»
 4604    "});
 4605
 4606    // Skip testing shuffle_line()
 4607
 4608    // From here on out, test more complex cases of manipulate_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 4609    // Since all methods calling manipulate_immutable_lines() are doing the exact same general thing (reordering lines)
 4610
 4611    // Don't manipulate when cursor is on single line, but expand the selection
 4612    cx.set_state(indoc! {"
 4613        ddˇdd
 4614        ccc
 4615        bb
 4616        a
 4617    "});
 4618    cx.update_editor(|e, window, cx| {
 4619        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4620    });
 4621    cx.assert_editor_state(indoc! {"
 4622        «ddddˇ»
 4623        ccc
 4624        bb
 4625        a
 4626    "});
 4627
 4628    // Basic manipulate case
 4629    // Start selection moves to column 0
 4630    // End of selection shrinks to fit shorter line
 4631    cx.set_state(indoc! {"
 4632        dd«d
 4633        ccc
 4634        bb
 4635        aaaaaˇ»
 4636    "});
 4637    cx.update_editor(|e, window, cx| {
 4638        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4639    });
 4640    cx.assert_editor_state(indoc! {"
 4641        «aaaaa
 4642        bb
 4643        ccc
 4644        dddˇ»
 4645    "});
 4646
 4647    // Manipulate case with newlines
 4648    cx.set_state(indoc! {"
 4649        dd«d
 4650        ccc
 4651
 4652        bb
 4653        aaaaa
 4654
 4655        ˇ»
 4656    "});
 4657    cx.update_editor(|e, window, cx| {
 4658        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4659    });
 4660    cx.assert_editor_state(indoc! {"
 4661        «
 4662
 4663        aaaaa
 4664        bb
 4665        ccc
 4666        dddˇ»
 4667
 4668    "});
 4669
 4670    // Adding new line
 4671    cx.set_state(indoc! {"
 4672        aa«a
 4673        bbˇ»b
 4674    "});
 4675    cx.update_editor(|e, window, cx| {
 4676        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added_line"))
 4677    });
 4678    cx.assert_editor_state(indoc! {"
 4679        «aaa
 4680        bbb
 4681        added_lineˇ»
 4682    "});
 4683
 4684    // Removing line
 4685    cx.set_state(indoc! {"
 4686        aa«a
 4687        bbbˇ»
 4688    "});
 4689    cx.update_editor(|e, window, cx| {
 4690        e.manipulate_immutable_lines(window, cx, |lines| {
 4691            lines.pop();
 4692        })
 4693    });
 4694    cx.assert_editor_state(indoc! {"
 4695        «aaaˇ»
 4696    "});
 4697
 4698    // Removing all lines
 4699    cx.set_state(indoc! {"
 4700        aa«a
 4701        bbbˇ»
 4702    "});
 4703    cx.update_editor(|e, window, cx| {
 4704        e.manipulate_immutable_lines(window, cx, |lines| {
 4705            lines.drain(..);
 4706        })
 4707    });
 4708    cx.assert_editor_state(indoc! {"
 4709        ˇ
 4710    "});
 4711}
 4712
 4713#[gpui::test]
 4714async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 4715    init_test(cx, |_| {});
 4716
 4717    let mut cx = EditorTestContext::new(cx).await;
 4718
 4719    // Consider continuous selection as single selection
 4720    cx.set_state(indoc! {"
 4721        Aaa«aa
 4722        cˇ»c«c
 4723        bb
 4724        aaaˇ»aa
 4725    "});
 4726    cx.update_editor(|e, window, cx| {
 4727        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4728    });
 4729    cx.assert_editor_state(indoc! {"
 4730        «Aaaaa
 4731        ccc
 4732        bb
 4733        aaaaaˇ»
 4734    "});
 4735
 4736    cx.set_state(indoc! {"
 4737        Aaa«aa
 4738        cˇ»c«c
 4739        bb
 4740        aaaˇ»aa
 4741    "});
 4742    cx.update_editor(|e, window, cx| {
 4743        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4744    });
 4745    cx.assert_editor_state(indoc! {"
 4746        «Aaaaa
 4747        ccc
 4748        bbˇ»
 4749    "});
 4750
 4751    // Consider non continuous selection as distinct dedup operations
 4752    cx.set_state(indoc! {"
 4753        «aaaaa
 4754        bb
 4755        aaaaa
 4756        aaaaaˇ»
 4757
 4758        aaa«aaˇ»
 4759    "});
 4760    cx.update_editor(|e, window, cx| {
 4761        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4762    });
 4763    cx.assert_editor_state(indoc! {"
 4764        «aaaaa
 4765        bbˇ»
 4766
 4767        «aaaaaˇ»
 4768    "});
 4769}
 4770
 4771#[gpui::test]
 4772async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 4773    init_test(cx, |_| {});
 4774
 4775    let mut cx = EditorTestContext::new(cx).await;
 4776
 4777    cx.set_state(indoc! {"
 4778        «Aaa
 4779        aAa
 4780        Aaaˇ»
 4781    "});
 4782    cx.update_editor(|e, window, cx| {
 4783        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4784    });
 4785    cx.assert_editor_state(indoc! {"
 4786        «Aaa
 4787        aAaˇ»
 4788    "});
 4789
 4790    cx.set_state(indoc! {"
 4791        «Aaa
 4792        aAa
 4793        aaAˇ»
 4794    "});
 4795    cx.update_editor(|e, window, cx| {
 4796        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4797    });
 4798    cx.assert_editor_state(indoc! {"
 4799        «Aaaˇ»
 4800    "});
 4801}
 4802
 4803#[gpui::test]
 4804async fn test_wrap_in_tag_single_selection(cx: &mut TestAppContext) {
 4805    init_test(cx, |_| {});
 4806
 4807    let mut cx = EditorTestContext::new(cx).await;
 4808
 4809    let js_language = Arc::new(Language::new(
 4810        LanguageConfig {
 4811            name: "JavaScript".into(),
 4812            wrap_characters: Some(language::WrapCharactersConfig {
 4813                start_prefix: "<".into(),
 4814                start_suffix: ">".into(),
 4815                end_prefix: "</".into(),
 4816                end_suffix: ">".into(),
 4817            }),
 4818            ..LanguageConfig::default()
 4819        },
 4820        None,
 4821    ));
 4822
 4823    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 4824
 4825    cx.set_state(indoc! {"
 4826        «testˇ»
 4827    "});
 4828    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4829    cx.assert_editor_state(indoc! {"
 4830        <«ˇ»>test</«ˇ»>
 4831    "});
 4832
 4833    cx.set_state(indoc! {"
 4834        «test
 4835         testˇ»
 4836    "});
 4837    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4838    cx.assert_editor_state(indoc! {"
 4839        <«ˇ»>test
 4840         test</«ˇ»>
 4841    "});
 4842
 4843    cx.set_state(indoc! {"
 4844        teˇst
 4845    "});
 4846    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4847    cx.assert_editor_state(indoc! {"
 4848        te<«ˇ»></«ˇ»>st
 4849    "});
 4850}
 4851
 4852#[gpui::test]
 4853async fn test_wrap_in_tag_multi_selection(cx: &mut TestAppContext) {
 4854    init_test(cx, |_| {});
 4855
 4856    let mut cx = EditorTestContext::new(cx).await;
 4857
 4858    let js_language = Arc::new(Language::new(
 4859        LanguageConfig {
 4860            name: "JavaScript".into(),
 4861            wrap_characters: Some(language::WrapCharactersConfig {
 4862                start_prefix: "<".into(),
 4863                start_suffix: ">".into(),
 4864                end_prefix: "</".into(),
 4865                end_suffix: ">".into(),
 4866            }),
 4867            ..LanguageConfig::default()
 4868        },
 4869        None,
 4870    ));
 4871
 4872    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 4873
 4874    cx.set_state(indoc! {"
 4875        «testˇ»
 4876        «testˇ» «testˇ»
 4877        «testˇ»
 4878    "});
 4879    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4880    cx.assert_editor_state(indoc! {"
 4881        <«ˇ»>test</«ˇ»>
 4882        <«ˇ»>test</«ˇ»> <«ˇ»>test</«ˇ»>
 4883        <«ˇ»>test</«ˇ»>
 4884    "});
 4885
 4886    cx.set_state(indoc! {"
 4887        «test
 4888         testˇ»
 4889        «test
 4890         testˇ»
 4891    "});
 4892    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4893    cx.assert_editor_state(indoc! {"
 4894        <«ˇ»>test
 4895         test</«ˇ»>
 4896        <«ˇ»>test
 4897         test</«ˇ»>
 4898    "});
 4899}
 4900
 4901#[gpui::test]
 4902async fn test_wrap_in_tag_does_nothing_in_unsupported_languages(cx: &mut TestAppContext) {
 4903    init_test(cx, |_| {});
 4904
 4905    let mut cx = EditorTestContext::new(cx).await;
 4906
 4907    let plaintext_language = Arc::new(Language::new(
 4908        LanguageConfig {
 4909            name: "Plain Text".into(),
 4910            ..LanguageConfig::default()
 4911        },
 4912        None,
 4913    ));
 4914
 4915    cx.update_buffer(|buffer, cx| buffer.set_language(Some(plaintext_language), cx));
 4916
 4917    cx.set_state(indoc! {"
 4918        «testˇ»
 4919    "});
 4920    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4921    cx.assert_editor_state(indoc! {"
 4922      «testˇ»
 4923    "});
 4924}
 4925
 4926#[gpui::test]
 4927async fn test_manipulate_immutable_lines_with_multi_selection(cx: &mut TestAppContext) {
 4928    init_test(cx, |_| {});
 4929
 4930    let mut cx = EditorTestContext::new(cx).await;
 4931
 4932    // Manipulate with multiple selections on a single line
 4933    cx.set_state(indoc! {"
 4934        dd«dd
 4935        cˇ»c«c
 4936        bb
 4937        aaaˇ»aa
 4938    "});
 4939    cx.update_editor(|e, window, cx| {
 4940        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4941    });
 4942    cx.assert_editor_state(indoc! {"
 4943        «aaaaa
 4944        bb
 4945        ccc
 4946        ddddˇ»
 4947    "});
 4948
 4949    // Manipulate with multiple disjoin selections
 4950    cx.set_state(indoc! {"
 4951 4952        4
 4953        3
 4954        2
 4955        1ˇ»
 4956
 4957        dd«dd
 4958        ccc
 4959        bb
 4960        aaaˇ»aa
 4961    "});
 4962    cx.update_editor(|e, window, cx| {
 4963        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4964    });
 4965    cx.assert_editor_state(indoc! {"
 4966        «1
 4967        2
 4968        3
 4969        4
 4970        5ˇ»
 4971
 4972        «aaaaa
 4973        bb
 4974        ccc
 4975        ddddˇ»
 4976    "});
 4977
 4978    // Adding lines on each selection
 4979    cx.set_state(indoc! {"
 4980 4981        1ˇ»
 4982
 4983        bb«bb
 4984        aaaˇ»aa
 4985    "});
 4986    cx.update_editor(|e, window, cx| {
 4987        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added line"))
 4988    });
 4989    cx.assert_editor_state(indoc! {"
 4990        «2
 4991        1
 4992        added lineˇ»
 4993
 4994        «bbbb
 4995        aaaaa
 4996        added lineˇ»
 4997    "});
 4998
 4999    // Removing lines on each selection
 5000    cx.set_state(indoc! {"
 5001 5002        1ˇ»
 5003
 5004        bb«bb
 5005        aaaˇ»aa
 5006    "});
 5007    cx.update_editor(|e, window, cx| {
 5008        e.manipulate_immutable_lines(window, cx, |lines| {
 5009            lines.pop();
 5010        })
 5011    });
 5012    cx.assert_editor_state(indoc! {"
 5013        «2ˇ»
 5014
 5015        «bbbbˇ»
 5016    "});
 5017}
 5018
 5019#[gpui::test]
 5020async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 5021    init_test(cx, |settings| {
 5022        settings.defaults.tab_size = NonZeroU32::new(3)
 5023    });
 5024
 5025    let mut cx = EditorTestContext::new(cx).await;
 5026
 5027    // MULTI SELECTION
 5028    // Ln.1 "«" tests empty lines
 5029    // Ln.9 tests just leading whitespace
 5030    cx.set_state(indoc! {"
 5031        «
 5032        abc                 // No indentationˇ»
 5033        «\tabc              // 1 tabˇ»
 5034        \t\tabc «      ˇ»   // 2 tabs
 5035        \t ab«c             // Tab followed by space
 5036         \tabc              // Space followed by tab (3 spaces should be the result)
 5037        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5038           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 5039        \t
 5040        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5041    "});
 5042    cx.update_editor(|e, window, cx| {
 5043        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5044    });
 5045    cx.assert_editor_state(
 5046        indoc! {"
 5047            «
 5048            abc                 // No indentation
 5049               abc              // 1 tab
 5050                  abc          // 2 tabs
 5051                abc             // Tab followed by space
 5052               abc              // Space followed by tab (3 spaces should be the result)
 5053                           abc   // Mixed indentation (tab conversion depends on the column)
 5054               abc         // Already space indented
 5055               ·
 5056               abc\tdef          // Only the leading tab is manipulatedˇ»
 5057        "}
 5058        .replace("·", "")
 5059        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5060    );
 5061
 5062    // Test on just a few lines, the others should remain unchanged
 5063    // Only lines (3, 5, 10, 11) should change
 5064    cx.set_state(
 5065        indoc! {"
 5066            ·
 5067            abc                 // No indentation
 5068            \tabcˇ               // 1 tab
 5069            \t\tabc             // 2 tabs
 5070            \t abcˇ              // Tab followed by space
 5071             \tabc              // Space followed by tab (3 spaces should be the result)
 5072            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5073               abc              // Already space indented
 5074            «\t
 5075            \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5076        "}
 5077        .replace("·", "")
 5078        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5079    );
 5080    cx.update_editor(|e, window, cx| {
 5081        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5082    });
 5083    cx.assert_editor_state(
 5084        indoc! {"
 5085            ·
 5086            abc                 // No indentation
 5087            «   abc               // 1 tabˇ»
 5088            \t\tabc             // 2 tabs
 5089            «    abc              // Tab followed by spaceˇ»
 5090             \tabc              // Space followed by tab (3 spaces should be the result)
 5091            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5092               abc              // Already space indented
 5093            «   ·
 5094               abc\tdef          // Only the leading tab is manipulatedˇ»
 5095        "}
 5096        .replace("·", "")
 5097        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5098    );
 5099
 5100    // SINGLE SELECTION
 5101    // Ln.1 "«" tests empty lines
 5102    // Ln.9 tests just leading whitespace
 5103    cx.set_state(indoc! {"
 5104        «
 5105        abc                 // No indentation
 5106        \tabc               // 1 tab
 5107        \t\tabc             // 2 tabs
 5108        \t abc              // Tab followed by space
 5109         \tabc              // Space followed by tab (3 spaces should be the result)
 5110        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5111           abc              // Already space indented
 5112        \t
 5113        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5114    "});
 5115    cx.update_editor(|e, window, cx| {
 5116        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5117    });
 5118    cx.assert_editor_state(
 5119        indoc! {"
 5120            «
 5121            abc                 // No indentation
 5122               abc               // 1 tab
 5123                  abc             // 2 tabs
 5124                abc              // Tab followed by space
 5125               abc              // Space followed by tab (3 spaces should be the result)
 5126                           abc   // Mixed indentation (tab conversion depends on the column)
 5127               abc              // Already space indented
 5128               ·
 5129               abc\tdef          // Only the leading tab is manipulatedˇ»
 5130        "}
 5131        .replace("·", "")
 5132        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5133    );
 5134}
 5135
 5136#[gpui::test]
 5137async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 5138    init_test(cx, |settings| {
 5139        settings.defaults.tab_size = NonZeroU32::new(3)
 5140    });
 5141
 5142    let mut cx = EditorTestContext::new(cx).await;
 5143
 5144    // MULTI SELECTION
 5145    // Ln.1 "«" tests empty lines
 5146    // Ln.11 tests just leading whitespace
 5147    cx.set_state(indoc! {"
 5148        «
 5149        abˇ»ˇc                 // No indentation
 5150         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 5151          abc  «             // 2 spaces (< 3 so dont convert)
 5152           abc              // 3 spaces (convert)
 5153             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 5154        «\tˇ»\t«\tˇ»abc           // Already tab indented
 5155        «\t abc              // Tab followed by space
 5156         \tabc              // Space followed by tab (should be consumed due to tab)
 5157        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5158           \tˇ»  «\t
 5159           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 5160    "});
 5161    cx.update_editor(|e, window, cx| {
 5162        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5163    });
 5164    cx.assert_editor_state(indoc! {"
 5165        «
 5166        abc                 // No indentation
 5167         abc                // 1 space (< 3 so dont convert)
 5168          abc               // 2 spaces (< 3 so dont convert)
 5169        \tabc              // 3 spaces (convert)
 5170        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5171        \t\t\tabc           // Already tab indented
 5172        \t abc              // Tab followed by space
 5173        \tabc              // Space followed by tab (should be consumed due to tab)
 5174        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5175        \t\t\t
 5176        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5177    "});
 5178
 5179    // Test on just a few lines, the other should remain unchanged
 5180    // Only lines (4, 8, 11, 12) should change
 5181    cx.set_state(
 5182        indoc! {"
 5183            ·
 5184            abc                 // No indentation
 5185             abc                // 1 space (< 3 so dont convert)
 5186              abc               // 2 spaces (< 3 so dont convert)
 5187            «   abc              // 3 spaces (convert)ˇ»
 5188                 abc            // 5 spaces (1 tab + 2 spaces)
 5189            \t\t\tabc           // Already tab indented
 5190            \t abc              // Tab followed by space
 5191             \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 5192               \t\t  \tabc      // Mixed indentation
 5193            \t \t  \t   \tabc   // Mixed indentation
 5194               \t  \tˇ
 5195            «   abc   \t         // Only the leading spaces should be convertedˇ»
 5196        "}
 5197        .replace("·", "")
 5198        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5199    );
 5200    cx.update_editor(|e, window, cx| {
 5201        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5202    });
 5203    cx.assert_editor_state(
 5204        indoc! {"
 5205            ·
 5206            abc                 // No indentation
 5207             abc                // 1 space (< 3 so dont convert)
 5208              abc               // 2 spaces (< 3 so dont convert)
 5209            «\tabc              // 3 spaces (convert)ˇ»
 5210                 abc            // 5 spaces (1 tab + 2 spaces)
 5211            \t\t\tabc           // Already tab indented
 5212            \t abc              // Tab followed by space
 5213            «\tabc              // Space followed by tab (should be consumed due to tab)ˇ»
 5214               \t\t  \tabc      // Mixed indentation
 5215            \t \t  \t   \tabc   // Mixed indentation
 5216            «\t\t\t
 5217            \tabc   \t         // Only the leading spaces should be convertedˇ»
 5218        "}
 5219        .replace("·", "")
 5220        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5221    );
 5222
 5223    // SINGLE SELECTION
 5224    // Ln.1 "«" tests empty lines
 5225    // Ln.11 tests just leading whitespace
 5226    cx.set_state(indoc! {"
 5227        «
 5228        abc                 // No indentation
 5229         abc                // 1 space (< 3 so dont convert)
 5230          abc               // 2 spaces (< 3 so dont convert)
 5231           abc              // 3 spaces (convert)
 5232             abc            // 5 spaces (1 tab + 2 spaces)
 5233        \t\t\tabc           // Already tab indented
 5234        \t abc              // Tab followed by space
 5235         \tabc              // Space followed by tab (should be consumed due to tab)
 5236        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5237           \t  \t
 5238           abc   \t         // Only the leading spaces should be convertedˇ»
 5239    "});
 5240    cx.update_editor(|e, window, cx| {
 5241        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5242    });
 5243    cx.assert_editor_state(indoc! {"
 5244        «
 5245        abc                 // No indentation
 5246         abc                // 1 space (< 3 so dont convert)
 5247          abc               // 2 spaces (< 3 so dont convert)
 5248        \tabc              // 3 spaces (convert)
 5249        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5250        \t\t\tabc           // Already tab indented
 5251        \t abc              // Tab followed by space
 5252        \tabc              // Space followed by tab (should be consumed due to tab)
 5253        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5254        \t\t\t
 5255        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5256    "});
 5257}
 5258
 5259#[gpui::test]
 5260async fn test_toggle_case(cx: &mut TestAppContext) {
 5261    init_test(cx, |_| {});
 5262
 5263    let mut cx = EditorTestContext::new(cx).await;
 5264
 5265    // If all lower case -> upper case
 5266    cx.set_state(indoc! {"
 5267        «hello worldˇ»
 5268    "});
 5269    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5270    cx.assert_editor_state(indoc! {"
 5271        «HELLO WORLDˇ»
 5272    "});
 5273
 5274    // If all upper case -> lower case
 5275    cx.set_state(indoc! {"
 5276        «HELLO WORLDˇ»
 5277    "});
 5278    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5279    cx.assert_editor_state(indoc! {"
 5280        «hello worldˇ»
 5281    "});
 5282
 5283    // If any upper case characters are identified -> lower case
 5284    // This matches JetBrains IDEs
 5285    cx.set_state(indoc! {"
 5286        «hEllo worldˇ»
 5287    "});
 5288    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5289    cx.assert_editor_state(indoc! {"
 5290        «hello worldˇ»
 5291    "});
 5292}
 5293
 5294#[gpui::test]
 5295async fn test_convert_to_sentence_case(cx: &mut TestAppContext) {
 5296    init_test(cx, |_| {});
 5297
 5298    let mut cx = EditorTestContext::new(cx).await;
 5299
 5300    cx.set_state(indoc! {"
 5301        «implement-windows-supportˇ»
 5302    "});
 5303    cx.update_editor(|e, window, cx| {
 5304        e.convert_to_sentence_case(&ConvertToSentenceCase, window, cx)
 5305    });
 5306    cx.assert_editor_state(indoc! {"
 5307        «Implement windows supportˇ»
 5308    "});
 5309}
 5310
 5311#[gpui::test]
 5312async fn test_manipulate_text(cx: &mut TestAppContext) {
 5313    init_test(cx, |_| {});
 5314
 5315    let mut cx = EditorTestContext::new(cx).await;
 5316
 5317    // Test convert_to_upper_case()
 5318    cx.set_state(indoc! {"
 5319        «hello worldˇ»
 5320    "});
 5321    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5322    cx.assert_editor_state(indoc! {"
 5323        «HELLO WORLDˇ»
 5324    "});
 5325
 5326    // Test convert_to_lower_case()
 5327    cx.set_state(indoc! {"
 5328        «HELLO WORLDˇ»
 5329    "});
 5330    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 5331    cx.assert_editor_state(indoc! {"
 5332        «hello worldˇ»
 5333    "});
 5334
 5335    // Test multiple line, single selection case
 5336    cx.set_state(indoc! {"
 5337        «The quick brown
 5338        fox jumps over
 5339        the lazy dogˇ»
 5340    "});
 5341    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 5342    cx.assert_editor_state(indoc! {"
 5343        «The Quick Brown
 5344        Fox Jumps Over
 5345        The Lazy Dogˇ»
 5346    "});
 5347
 5348    // Test multiple line, single selection case
 5349    cx.set_state(indoc! {"
 5350        «The quick brown
 5351        fox jumps over
 5352        the lazy dogˇ»
 5353    "});
 5354    cx.update_editor(|e, window, cx| {
 5355        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 5356    });
 5357    cx.assert_editor_state(indoc! {"
 5358        «TheQuickBrown
 5359        FoxJumpsOver
 5360        TheLazyDogˇ»
 5361    "});
 5362
 5363    // From here on out, test more complex cases of manipulate_text()
 5364
 5365    // Test no selection case - should affect words cursors are in
 5366    // Cursor at beginning, middle, and end of word
 5367    cx.set_state(indoc! {"
 5368        ˇhello big beauˇtiful worldˇ
 5369    "});
 5370    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5371    cx.assert_editor_state(indoc! {"
 5372        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 5373    "});
 5374
 5375    // Test multiple selections on a single line and across multiple lines
 5376    cx.set_state(indoc! {"
 5377        «Theˇ» quick «brown
 5378        foxˇ» jumps «overˇ»
 5379        the «lazyˇ» dog
 5380    "});
 5381    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5382    cx.assert_editor_state(indoc! {"
 5383        «THEˇ» quick «BROWN
 5384        FOXˇ» jumps «OVERˇ»
 5385        the «LAZYˇ» dog
 5386    "});
 5387
 5388    // Test case where text length grows
 5389    cx.set_state(indoc! {"
 5390        «tschüߡ»
 5391    "});
 5392    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5393    cx.assert_editor_state(indoc! {"
 5394        «TSCHÜSSˇ»
 5395    "});
 5396
 5397    // Test to make sure we don't crash when text shrinks
 5398    cx.set_state(indoc! {"
 5399        aaa_bbbˇ
 5400    "});
 5401    cx.update_editor(|e, window, cx| {
 5402        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5403    });
 5404    cx.assert_editor_state(indoc! {"
 5405        «aaaBbbˇ»
 5406    "});
 5407
 5408    // Test to make sure we all aware of the fact that each word can grow and shrink
 5409    // Final selections should be aware of this fact
 5410    cx.set_state(indoc! {"
 5411        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 5412    "});
 5413    cx.update_editor(|e, window, cx| {
 5414        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5415    });
 5416    cx.assert_editor_state(indoc! {"
 5417        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 5418    "});
 5419
 5420    cx.set_state(indoc! {"
 5421        «hElLo, WoRld!ˇ»
 5422    "});
 5423    cx.update_editor(|e, window, cx| {
 5424        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 5425    });
 5426    cx.assert_editor_state(indoc! {"
 5427        «HeLlO, wOrLD!ˇ»
 5428    "});
 5429
 5430    // Test selections with `line_mode() = true`.
 5431    cx.update_editor(|editor, _window, _cx| editor.selections.set_line_mode(true));
 5432    cx.set_state(indoc! {"
 5433        «The quick brown
 5434        fox jumps over
 5435        tˇ»he lazy dog
 5436    "});
 5437    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5438    cx.assert_editor_state(indoc! {"
 5439        «THE QUICK BROWN
 5440        FOX JUMPS OVER
 5441        THE LAZY DOGˇ»
 5442    "});
 5443}
 5444
 5445#[gpui::test]
 5446fn test_duplicate_line(cx: &mut TestAppContext) {
 5447    init_test(cx, |_| {});
 5448
 5449    let editor = cx.add_window(|window, cx| {
 5450        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5451        build_editor(buffer, window, cx)
 5452    });
 5453    _ = editor.update(cx, |editor, window, cx| {
 5454        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5455            s.select_display_ranges([
 5456                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5457                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5458                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5459                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5460            ])
 5461        });
 5462        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5463        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5464        assert_eq!(
 5465            editor.selections.display_ranges(cx),
 5466            vec![
 5467                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 5468                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 5469                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5470                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 5471            ]
 5472        );
 5473    });
 5474
 5475    let editor = cx.add_window(|window, cx| {
 5476        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5477        build_editor(buffer, window, cx)
 5478    });
 5479    _ = editor.update(cx, |editor, window, cx| {
 5480        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5481            s.select_display_ranges([
 5482                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5483                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5484            ])
 5485        });
 5486        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5487        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5488        assert_eq!(
 5489            editor.selections.display_ranges(cx),
 5490            vec![
 5491                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 5492                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 5493            ]
 5494        );
 5495    });
 5496
 5497    // With `move_upwards` the selections stay in place, except for
 5498    // the lines inserted above them
 5499    let editor = cx.add_window(|window, cx| {
 5500        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5501        build_editor(buffer, window, cx)
 5502    });
 5503    _ = editor.update(cx, |editor, window, cx| {
 5504        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5505            s.select_display_ranges([
 5506                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5507                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5508                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5509                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5510            ])
 5511        });
 5512        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5513        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5514        assert_eq!(
 5515            editor.selections.display_ranges(cx),
 5516            vec![
 5517                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5518                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5519                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 5520                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 5521            ]
 5522        );
 5523    });
 5524
 5525    let editor = cx.add_window(|window, cx| {
 5526        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5527        build_editor(buffer, window, cx)
 5528    });
 5529    _ = editor.update(cx, |editor, window, cx| {
 5530        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5531            s.select_display_ranges([
 5532                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5533                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5534            ])
 5535        });
 5536        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5537        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5538        assert_eq!(
 5539            editor.selections.display_ranges(cx),
 5540            vec![
 5541                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5542                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5543            ]
 5544        );
 5545    });
 5546
 5547    let editor = cx.add_window(|window, cx| {
 5548        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5549        build_editor(buffer, window, cx)
 5550    });
 5551    _ = editor.update(cx, |editor, window, cx| {
 5552        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5553            s.select_display_ranges([
 5554                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5555                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5556            ])
 5557        });
 5558        editor.duplicate_selection(&DuplicateSelection, window, cx);
 5559        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 5560        assert_eq!(
 5561            editor.selections.display_ranges(cx),
 5562            vec![
 5563                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5564                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 5565            ]
 5566        );
 5567    });
 5568}
 5569
 5570#[gpui::test]
 5571fn test_move_line_up_down(cx: &mut TestAppContext) {
 5572    init_test(cx, |_| {});
 5573
 5574    let editor = cx.add_window(|window, cx| {
 5575        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5576        build_editor(buffer, window, cx)
 5577    });
 5578    _ = editor.update(cx, |editor, window, cx| {
 5579        editor.fold_creases(
 5580            vec![
 5581                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 5582                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 5583                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 5584            ],
 5585            true,
 5586            window,
 5587            cx,
 5588        );
 5589        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5590            s.select_display_ranges([
 5591                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5592                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5593                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5594                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 5595            ])
 5596        });
 5597        assert_eq!(
 5598            editor.display_text(cx),
 5599            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 5600        );
 5601
 5602        editor.move_line_up(&MoveLineUp, window, cx);
 5603        assert_eq!(
 5604            editor.display_text(cx),
 5605            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 5606        );
 5607        assert_eq!(
 5608            editor.selections.display_ranges(cx),
 5609            vec![
 5610                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5611                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5612                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5613                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5614            ]
 5615        );
 5616    });
 5617
 5618    _ = editor.update(cx, |editor, window, cx| {
 5619        editor.move_line_down(&MoveLineDown, window, cx);
 5620        assert_eq!(
 5621            editor.display_text(cx),
 5622            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 5623        );
 5624        assert_eq!(
 5625            editor.selections.display_ranges(cx),
 5626            vec![
 5627                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5628                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5629                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5630                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5631            ]
 5632        );
 5633    });
 5634
 5635    _ = editor.update(cx, |editor, window, cx| {
 5636        editor.move_line_down(&MoveLineDown, window, cx);
 5637        assert_eq!(
 5638            editor.display_text(cx),
 5639            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 5640        );
 5641        assert_eq!(
 5642            editor.selections.display_ranges(cx),
 5643            vec![
 5644                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5645                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5646                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5647                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5648            ]
 5649        );
 5650    });
 5651
 5652    _ = editor.update(cx, |editor, window, cx| {
 5653        editor.move_line_up(&MoveLineUp, window, cx);
 5654        assert_eq!(
 5655            editor.display_text(cx),
 5656            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 5657        );
 5658        assert_eq!(
 5659            editor.selections.display_ranges(cx),
 5660            vec![
 5661                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5662                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5663                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5664                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5665            ]
 5666        );
 5667    });
 5668}
 5669
 5670#[gpui::test]
 5671fn test_move_line_up_selection_at_end_of_fold(cx: &mut TestAppContext) {
 5672    init_test(cx, |_| {});
 5673    let editor = cx.add_window(|window, cx| {
 5674        let buffer = MultiBuffer::build_simple("\n\n\n\n\n\naaaa\nbbbb\ncccc", cx);
 5675        build_editor(buffer, window, cx)
 5676    });
 5677    _ = editor.update(cx, |editor, window, cx| {
 5678        editor.fold_creases(
 5679            vec![Crease::simple(
 5680                Point::new(6, 4)..Point::new(7, 4),
 5681                FoldPlaceholder::test(),
 5682            )],
 5683            true,
 5684            window,
 5685            cx,
 5686        );
 5687        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5688            s.select_ranges([Point::new(7, 4)..Point::new(7, 4)])
 5689        });
 5690        assert_eq!(editor.display_text(cx), "\n\n\n\n\n\naaaa⋯\ncccc");
 5691        editor.move_line_up(&MoveLineUp, window, cx);
 5692        let buffer_text = editor.buffer.read(cx).snapshot(cx).text();
 5693        assert_eq!(buffer_text, "\n\n\n\n\naaaa\nbbbb\n\ncccc");
 5694    });
 5695}
 5696
 5697#[gpui::test]
 5698fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 5699    init_test(cx, |_| {});
 5700
 5701    let editor = cx.add_window(|window, cx| {
 5702        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5703        build_editor(buffer, window, cx)
 5704    });
 5705    _ = editor.update(cx, |editor, window, cx| {
 5706        let snapshot = editor.buffer.read(cx).snapshot(cx);
 5707        editor.insert_blocks(
 5708            [BlockProperties {
 5709                style: BlockStyle::Fixed,
 5710                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 5711                height: Some(1),
 5712                render: Arc::new(|_| div().into_any()),
 5713                priority: 0,
 5714            }],
 5715            Some(Autoscroll::fit()),
 5716            cx,
 5717        );
 5718        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5719            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 5720        });
 5721        editor.move_line_down(&MoveLineDown, window, cx);
 5722    });
 5723}
 5724
 5725#[gpui::test]
 5726async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 5727    init_test(cx, |_| {});
 5728
 5729    let mut cx = EditorTestContext::new(cx).await;
 5730    cx.set_state(
 5731        &"
 5732            ˇzero
 5733            one
 5734            two
 5735            three
 5736            four
 5737            five
 5738        "
 5739        .unindent(),
 5740    );
 5741
 5742    // Create a four-line block that replaces three lines of text.
 5743    cx.update_editor(|editor, window, cx| {
 5744        let snapshot = editor.snapshot(window, cx);
 5745        let snapshot = &snapshot.buffer_snapshot();
 5746        let placement = BlockPlacement::Replace(
 5747            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 5748        );
 5749        editor.insert_blocks(
 5750            [BlockProperties {
 5751                placement,
 5752                height: Some(4),
 5753                style: BlockStyle::Sticky,
 5754                render: Arc::new(|_| gpui::div().into_any_element()),
 5755                priority: 0,
 5756            }],
 5757            None,
 5758            cx,
 5759        );
 5760    });
 5761
 5762    // Move down so that the cursor touches the block.
 5763    cx.update_editor(|editor, window, cx| {
 5764        editor.move_down(&Default::default(), window, cx);
 5765    });
 5766    cx.assert_editor_state(
 5767        &"
 5768            zero
 5769            «one
 5770            two
 5771            threeˇ»
 5772            four
 5773            five
 5774        "
 5775        .unindent(),
 5776    );
 5777
 5778    // Move down past the block.
 5779    cx.update_editor(|editor, window, cx| {
 5780        editor.move_down(&Default::default(), window, cx);
 5781    });
 5782    cx.assert_editor_state(
 5783        &"
 5784            zero
 5785            one
 5786            two
 5787            three
 5788            ˇfour
 5789            five
 5790        "
 5791        .unindent(),
 5792    );
 5793}
 5794
 5795#[gpui::test]
 5796fn test_transpose(cx: &mut TestAppContext) {
 5797    init_test(cx, |_| {});
 5798
 5799    _ = cx.add_window(|window, cx| {
 5800        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 5801        editor.set_style(EditorStyle::default(), window, cx);
 5802        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5803            s.select_ranges([1..1])
 5804        });
 5805        editor.transpose(&Default::default(), window, cx);
 5806        assert_eq!(editor.text(cx), "bac");
 5807        assert_eq!(editor.selections.ranges(cx), [2..2]);
 5808
 5809        editor.transpose(&Default::default(), window, cx);
 5810        assert_eq!(editor.text(cx), "bca");
 5811        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5812
 5813        editor.transpose(&Default::default(), window, cx);
 5814        assert_eq!(editor.text(cx), "bac");
 5815        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5816
 5817        editor
 5818    });
 5819
 5820    _ = cx.add_window(|window, cx| {
 5821        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5822        editor.set_style(EditorStyle::default(), window, cx);
 5823        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5824            s.select_ranges([3..3])
 5825        });
 5826        editor.transpose(&Default::default(), window, cx);
 5827        assert_eq!(editor.text(cx), "acb\nde");
 5828        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5829
 5830        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5831            s.select_ranges([4..4])
 5832        });
 5833        editor.transpose(&Default::default(), window, cx);
 5834        assert_eq!(editor.text(cx), "acbd\ne");
 5835        assert_eq!(editor.selections.ranges(cx), [5..5]);
 5836
 5837        editor.transpose(&Default::default(), window, cx);
 5838        assert_eq!(editor.text(cx), "acbde\n");
 5839        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5840
 5841        editor.transpose(&Default::default(), window, cx);
 5842        assert_eq!(editor.text(cx), "acbd\ne");
 5843        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5844
 5845        editor
 5846    });
 5847
 5848    _ = cx.add_window(|window, cx| {
 5849        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5850        editor.set_style(EditorStyle::default(), window, cx);
 5851        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5852            s.select_ranges([1..1, 2..2, 4..4])
 5853        });
 5854        editor.transpose(&Default::default(), window, cx);
 5855        assert_eq!(editor.text(cx), "bacd\ne");
 5856        assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
 5857
 5858        editor.transpose(&Default::default(), window, cx);
 5859        assert_eq!(editor.text(cx), "bcade\n");
 5860        assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
 5861
 5862        editor.transpose(&Default::default(), window, cx);
 5863        assert_eq!(editor.text(cx), "bcda\ne");
 5864        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5865
 5866        editor.transpose(&Default::default(), window, cx);
 5867        assert_eq!(editor.text(cx), "bcade\n");
 5868        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5869
 5870        editor.transpose(&Default::default(), window, cx);
 5871        assert_eq!(editor.text(cx), "bcaed\n");
 5872        assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
 5873
 5874        editor
 5875    });
 5876
 5877    _ = cx.add_window(|window, cx| {
 5878        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 5879        editor.set_style(EditorStyle::default(), window, cx);
 5880        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5881            s.select_ranges([4..4])
 5882        });
 5883        editor.transpose(&Default::default(), window, cx);
 5884        assert_eq!(editor.text(cx), "🏀🍐✋");
 5885        assert_eq!(editor.selections.ranges(cx), [8..8]);
 5886
 5887        editor.transpose(&Default::default(), window, cx);
 5888        assert_eq!(editor.text(cx), "🏀✋🍐");
 5889        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5890
 5891        editor.transpose(&Default::default(), window, cx);
 5892        assert_eq!(editor.text(cx), "🏀🍐✋");
 5893        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5894
 5895        editor
 5896    });
 5897}
 5898
 5899#[gpui::test]
 5900async fn test_rewrap(cx: &mut TestAppContext) {
 5901    init_test(cx, |settings| {
 5902        settings.languages.0.extend([
 5903            (
 5904                "Markdown".into(),
 5905                LanguageSettingsContent {
 5906                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5907                    preferred_line_length: Some(40),
 5908                    ..Default::default()
 5909                },
 5910            ),
 5911            (
 5912                "Plain Text".into(),
 5913                LanguageSettingsContent {
 5914                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5915                    preferred_line_length: Some(40),
 5916                    ..Default::default()
 5917                },
 5918            ),
 5919            (
 5920                "C++".into(),
 5921                LanguageSettingsContent {
 5922                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5923                    preferred_line_length: Some(40),
 5924                    ..Default::default()
 5925                },
 5926            ),
 5927            (
 5928                "Python".into(),
 5929                LanguageSettingsContent {
 5930                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5931                    preferred_line_length: Some(40),
 5932                    ..Default::default()
 5933                },
 5934            ),
 5935            (
 5936                "Rust".into(),
 5937                LanguageSettingsContent {
 5938                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5939                    preferred_line_length: Some(40),
 5940                    ..Default::default()
 5941                },
 5942            ),
 5943        ])
 5944    });
 5945
 5946    let mut cx = EditorTestContext::new(cx).await;
 5947
 5948    let cpp_language = Arc::new(Language::new(
 5949        LanguageConfig {
 5950            name: "C++".into(),
 5951            line_comments: vec!["// ".into()],
 5952            ..LanguageConfig::default()
 5953        },
 5954        None,
 5955    ));
 5956    let python_language = Arc::new(Language::new(
 5957        LanguageConfig {
 5958            name: "Python".into(),
 5959            line_comments: vec!["# ".into()],
 5960            ..LanguageConfig::default()
 5961        },
 5962        None,
 5963    ));
 5964    let markdown_language = Arc::new(Language::new(
 5965        LanguageConfig {
 5966            name: "Markdown".into(),
 5967            rewrap_prefixes: vec![
 5968                regex::Regex::new("\\d+\\.\\s+").unwrap(),
 5969                regex::Regex::new("[-*+]\\s+").unwrap(),
 5970            ],
 5971            ..LanguageConfig::default()
 5972        },
 5973        None,
 5974    ));
 5975    let rust_language = Arc::new(
 5976        Language::new(
 5977            LanguageConfig {
 5978                name: "Rust".into(),
 5979                line_comments: vec!["// ".into(), "/// ".into()],
 5980                ..LanguageConfig::default()
 5981            },
 5982            Some(tree_sitter_rust::LANGUAGE.into()),
 5983        )
 5984        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 5985        .unwrap(),
 5986    );
 5987
 5988    let plaintext_language = Arc::new(Language::new(
 5989        LanguageConfig {
 5990            name: "Plain Text".into(),
 5991            ..LanguageConfig::default()
 5992        },
 5993        None,
 5994    ));
 5995
 5996    // Test basic rewrapping of a long line with a cursor
 5997    assert_rewrap(
 5998        indoc! {"
 5999            // ˇThis is a long comment that needs to be wrapped.
 6000        "},
 6001        indoc! {"
 6002            // ˇThis is a long comment that needs to
 6003            // be wrapped.
 6004        "},
 6005        cpp_language.clone(),
 6006        &mut cx,
 6007    );
 6008
 6009    // Test rewrapping a full selection
 6010    assert_rewrap(
 6011        indoc! {"
 6012            «// This selected long comment needs to be wrapped.ˇ»"
 6013        },
 6014        indoc! {"
 6015            «// This selected long comment needs to
 6016            // be wrapped.ˇ»"
 6017        },
 6018        cpp_language.clone(),
 6019        &mut cx,
 6020    );
 6021
 6022    // Test multiple cursors on different lines within the same paragraph are preserved after rewrapping
 6023    assert_rewrap(
 6024        indoc! {"
 6025            // ˇThis is the first line.
 6026            // Thisˇ is the second line.
 6027            // This is the thirdˇ line, all part of one paragraph.
 6028         "},
 6029        indoc! {"
 6030            // ˇThis is the first line. Thisˇ is the
 6031            // second line. This is the thirdˇ line,
 6032            // all part of one paragraph.
 6033         "},
 6034        cpp_language.clone(),
 6035        &mut cx,
 6036    );
 6037
 6038    // Test multiple cursors in different paragraphs trigger separate rewraps
 6039    assert_rewrap(
 6040        indoc! {"
 6041            // ˇThis is the first paragraph, first line.
 6042            // ˇThis is the first paragraph, second line.
 6043
 6044            // ˇThis is the second paragraph, first line.
 6045            // ˇThis is the second paragraph, second line.
 6046        "},
 6047        indoc! {"
 6048            // ˇThis is the first paragraph, first
 6049            // line. ˇThis is the first paragraph,
 6050            // second line.
 6051
 6052            // ˇThis is the second paragraph, first
 6053            // line. ˇThis is the second paragraph,
 6054            // second line.
 6055        "},
 6056        cpp_language.clone(),
 6057        &mut cx,
 6058    );
 6059
 6060    // Test that change in comment prefix (e.g., `//` to `///`) trigger seperate rewraps
 6061    assert_rewrap(
 6062        indoc! {"
 6063            «// A regular long long comment to be wrapped.
 6064            /// A documentation long comment to be wrapped.ˇ»
 6065          "},
 6066        indoc! {"
 6067            «// A regular long long comment to be
 6068            // wrapped.
 6069            /// A documentation long comment to be
 6070            /// wrapped.ˇ»
 6071          "},
 6072        rust_language.clone(),
 6073        &mut cx,
 6074    );
 6075
 6076    // Test that change in indentation level trigger seperate rewraps
 6077    assert_rewrap(
 6078        indoc! {"
 6079            fn foo() {
 6080                «// This is a long comment at the base indent.
 6081                    // This is a long comment at the next indent.ˇ»
 6082            }
 6083        "},
 6084        indoc! {"
 6085            fn foo() {
 6086                «// This is a long comment at the
 6087                // base indent.
 6088                    // This is a long comment at the
 6089                    // next indent.ˇ»
 6090            }
 6091        "},
 6092        rust_language.clone(),
 6093        &mut cx,
 6094    );
 6095
 6096    // Test that different comment prefix characters (e.g., '#') are handled correctly
 6097    assert_rewrap(
 6098        indoc! {"
 6099            # ˇThis is a long comment using a pound sign.
 6100        "},
 6101        indoc! {"
 6102            # ˇThis is a long comment using a pound
 6103            # sign.
 6104        "},
 6105        python_language,
 6106        &mut cx,
 6107    );
 6108
 6109    // Test rewrapping only affects comments, not code even when selected
 6110    assert_rewrap(
 6111        indoc! {"
 6112            «/// This doc comment is long and should be wrapped.
 6113            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6114        "},
 6115        indoc! {"
 6116            «/// This doc comment is long and should
 6117            /// be wrapped.
 6118            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6119        "},
 6120        rust_language.clone(),
 6121        &mut cx,
 6122    );
 6123
 6124    // Test that rewrapping works in Markdown documents where `allow_rewrap` is `Anywhere`
 6125    assert_rewrap(
 6126        indoc! {"
 6127            # Header
 6128
 6129            A long long long line of markdown text to wrap.ˇ
 6130         "},
 6131        indoc! {"
 6132            # Header
 6133
 6134            A long long long line of markdown text
 6135            to wrap.ˇ
 6136         "},
 6137        markdown_language.clone(),
 6138        &mut cx,
 6139    );
 6140
 6141    // Test that rewrapping boundary works and preserves relative indent for Markdown documents
 6142    assert_rewrap(
 6143        indoc! {"
 6144            «1. This is a numbered list item that is very long and needs to be wrapped properly.
 6145            2. This is a numbered list item that is very long and needs to be wrapped properly.
 6146            - This is an unordered list item that is also very long and should not merge with the numbered item.ˇ»
 6147        "},
 6148        indoc! {"
 6149            «1. This is a numbered list item that is
 6150               very long and needs to be wrapped
 6151               properly.
 6152            2. This is a numbered list item that is
 6153               very long and needs to be wrapped
 6154               properly.
 6155            - This is an unordered list item that is
 6156              also very long and should not merge
 6157              with the numbered item.ˇ»
 6158        "},
 6159        markdown_language.clone(),
 6160        &mut cx,
 6161    );
 6162
 6163    // Test that rewrapping add indents for rewrapping boundary if not exists already.
 6164    assert_rewrap(
 6165        indoc! {"
 6166            «1. This is a numbered list item that is
 6167            very long and needs to be wrapped
 6168            properly.
 6169            2. This is a numbered list item that is
 6170            very long and needs to be wrapped
 6171            properly.
 6172            - This is an unordered list item that is
 6173            also very long and should not merge with
 6174            the numbered item.ˇ»
 6175        "},
 6176        indoc! {"
 6177            «1. This is a numbered list item that is
 6178               very long and needs to be wrapped
 6179               properly.
 6180            2. This is a numbered list item that is
 6181               very long and needs to be wrapped
 6182               properly.
 6183            - This is an unordered list item that is
 6184              also very long and should not merge
 6185              with the numbered item.ˇ»
 6186        "},
 6187        markdown_language.clone(),
 6188        &mut cx,
 6189    );
 6190
 6191    // Test that rewrapping maintain indents even when they already exists.
 6192    assert_rewrap(
 6193        indoc! {"
 6194            «1. This is a numbered list
 6195               item that is very long and needs to be wrapped properly.
 6196            2. This is a numbered list
 6197               item that is very long and needs to be wrapped properly.
 6198            - This is an unordered list item that is also very long and
 6199              should not merge with the numbered item.ˇ»
 6200        "},
 6201        indoc! {"
 6202            «1. This is a numbered list item that is
 6203               very long and needs to be wrapped
 6204               properly.
 6205            2. This is a numbered list item that is
 6206               very long and needs to be wrapped
 6207               properly.
 6208            - This is an unordered list item that is
 6209              also very long and should not merge
 6210              with the numbered item.ˇ»
 6211        "},
 6212        markdown_language,
 6213        &mut cx,
 6214    );
 6215
 6216    // Test that rewrapping works in plain text where `allow_rewrap` is `Anywhere`
 6217    assert_rewrap(
 6218        indoc! {"
 6219            ˇThis is a very long line of plain text that will be wrapped.
 6220        "},
 6221        indoc! {"
 6222            ˇThis is a very long line of plain text
 6223            that will be wrapped.
 6224        "},
 6225        plaintext_language.clone(),
 6226        &mut cx,
 6227    );
 6228
 6229    // Test that non-commented code acts as a paragraph boundary within a selection
 6230    assert_rewrap(
 6231        indoc! {"
 6232               «// This is the first long comment block to be wrapped.
 6233               fn my_func(a: u32);
 6234               // This is the second long comment block to be wrapped.ˇ»
 6235           "},
 6236        indoc! {"
 6237               «// This is the first long comment block
 6238               // to be wrapped.
 6239               fn my_func(a: u32);
 6240               // This is the second long comment block
 6241               // to be wrapped.ˇ»
 6242           "},
 6243        rust_language,
 6244        &mut cx,
 6245    );
 6246
 6247    // Test rewrapping multiple selections, including ones with blank lines or tabs
 6248    assert_rewrap(
 6249        indoc! {"
 6250            «ˇThis is a very long line that will be wrapped.
 6251
 6252            This is another paragraph in the same selection.»
 6253
 6254            «\tThis is a very long indented line that will be wrapped.ˇ»
 6255         "},
 6256        indoc! {"
 6257            «ˇThis is a very long line that will be
 6258            wrapped.
 6259
 6260            This is another paragraph in the same
 6261            selection.»
 6262
 6263            «\tThis is a very long indented line
 6264            \tthat will be wrapped.ˇ»
 6265         "},
 6266        plaintext_language,
 6267        &mut cx,
 6268    );
 6269
 6270    // Test that an empty comment line acts as a paragraph boundary
 6271    assert_rewrap(
 6272        indoc! {"
 6273            // ˇThis is a long comment that will be wrapped.
 6274            //
 6275            // And this is another long comment that will also be wrapped.ˇ
 6276         "},
 6277        indoc! {"
 6278            // ˇThis is a long comment that will be
 6279            // wrapped.
 6280            //
 6281            // And this is another long comment that
 6282            // will also be wrapped.ˇ
 6283         "},
 6284        cpp_language,
 6285        &mut cx,
 6286    );
 6287
 6288    #[track_caller]
 6289    fn assert_rewrap(
 6290        unwrapped_text: &str,
 6291        wrapped_text: &str,
 6292        language: Arc<Language>,
 6293        cx: &mut EditorTestContext,
 6294    ) {
 6295        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6296        cx.set_state(unwrapped_text);
 6297        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6298        cx.assert_editor_state(wrapped_text);
 6299    }
 6300}
 6301
 6302#[gpui::test]
 6303async fn test_rewrap_block_comments(cx: &mut TestAppContext) {
 6304    init_test(cx, |settings| {
 6305        settings.languages.0.extend([(
 6306            "Rust".into(),
 6307            LanguageSettingsContent {
 6308                allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6309                preferred_line_length: Some(40),
 6310                ..Default::default()
 6311            },
 6312        )])
 6313    });
 6314
 6315    let mut cx = EditorTestContext::new(cx).await;
 6316
 6317    let rust_lang = Arc::new(
 6318        Language::new(
 6319            LanguageConfig {
 6320                name: "Rust".into(),
 6321                line_comments: vec!["// ".into()],
 6322                block_comment: Some(BlockCommentConfig {
 6323                    start: "/*".into(),
 6324                    end: "*/".into(),
 6325                    prefix: "* ".into(),
 6326                    tab_size: 1,
 6327                }),
 6328                documentation_comment: Some(BlockCommentConfig {
 6329                    start: "/**".into(),
 6330                    end: "*/".into(),
 6331                    prefix: "* ".into(),
 6332                    tab_size: 1,
 6333                }),
 6334
 6335                ..LanguageConfig::default()
 6336            },
 6337            Some(tree_sitter_rust::LANGUAGE.into()),
 6338        )
 6339        .with_override_query("[(line_comment) (block_comment)] @comment.inclusive")
 6340        .unwrap(),
 6341    );
 6342
 6343    // regular block comment
 6344    assert_rewrap(
 6345        indoc! {"
 6346            /*
 6347             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6348             */
 6349            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6350        "},
 6351        indoc! {"
 6352            /*
 6353             *ˇ Lorem ipsum dolor sit amet,
 6354             * consectetur adipiscing elit.
 6355             */
 6356            /*
 6357             *ˇ Lorem ipsum dolor sit amet,
 6358             * consectetur adipiscing elit.
 6359             */
 6360        "},
 6361        rust_lang.clone(),
 6362        &mut cx,
 6363    );
 6364
 6365    // indent is respected
 6366    assert_rewrap(
 6367        indoc! {"
 6368            {}
 6369                /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6370        "},
 6371        indoc! {"
 6372            {}
 6373                /*
 6374                 *ˇ Lorem ipsum dolor sit amet,
 6375                 * consectetur adipiscing elit.
 6376                 */
 6377        "},
 6378        rust_lang.clone(),
 6379        &mut cx,
 6380    );
 6381
 6382    // short block comments with inline delimiters
 6383    assert_rewrap(
 6384        indoc! {"
 6385            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6386            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6387             */
 6388            /*
 6389             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 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             *ˇ Lorem ipsum dolor sit amet,
 6402             * consectetur adipiscing elit.
 6403             */
 6404        "},
 6405        rust_lang.clone(),
 6406        &mut cx,
 6407    );
 6408
 6409    // multiline block comment with inline start/end delimiters
 6410    assert_rewrap(
 6411        indoc! {"
 6412            /*ˇ Lorem ipsum dolor sit amet,
 6413             * consectetur adipiscing elit. */
 6414        "},
 6415        indoc! {"
 6416            /*
 6417             *ˇ Lorem ipsum dolor sit amet,
 6418             * consectetur adipiscing elit.
 6419             */
 6420        "},
 6421        rust_lang.clone(),
 6422        &mut cx,
 6423    );
 6424
 6425    // block comment rewrap still respects paragraph bounds
 6426    assert_rewrap(
 6427        indoc! {"
 6428            /*
 6429             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6430             *
 6431             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6432             */
 6433        "},
 6434        indoc! {"
 6435            /*
 6436             *ˇ Lorem ipsum dolor sit amet,
 6437             * consectetur adipiscing elit.
 6438             *
 6439             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6440             */
 6441        "},
 6442        rust_lang.clone(),
 6443        &mut cx,
 6444    );
 6445
 6446    // documentation comments
 6447    assert_rewrap(
 6448        indoc! {"
 6449            /**ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6450            /**
 6451             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6452             */
 6453        "},
 6454        indoc! {"
 6455            /**
 6456             *ˇ Lorem ipsum dolor sit amet,
 6457             * consectetur adipiscing elit.
 6458             */
 6459            /**
 6460             *ˇ Lorem ipsum dolor sit amet,
 6461             * consectetur adipiscing elit.
 6462             */
 6463        "},
 6464        rust_lang.clone(),
 6465        &mut cx,
 6466    );
 6467
 6468    // different, adjacent comments
 6469    assert_rewrap(
 6470        indoc! {"
 6471            /**
 6472             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6473             */
 6474            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6475            //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6476        "},
 6477        indoc! {"
 6478            /**
 6479             *ˇ Lorem ipsum dolor sit amet,
 6480             * consectetur adipiscing elit.
 6481             */
 6482            /*
 6483             *ˇ Lorem ipsum dolor sit amet,
 6484             * consectetur adipiscing elit.
 6485             */
 6486            //ˇ Lorem ipsum dolor sit amet,
 6487            // consectetur adipiscing elit.
 6488        "},
 6489        rust_lang.clone(),
 6490        &mut cx,
 6491    );
 6492
 6493    // selection w/ single short block comment
 6494    assert_rewrap(
 6495        indoc! {"
 6496            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6497        "},
 6498        indoc! {"
 6499            «/*
 6500             * Lorem ipsum dolor sit amet,
 6501             * consectetur adipiscing elit.
 6502             */ˇ»
 6503        "},
 6504        rust_lang.clone(),
 6505        &mut cx,
 6506    );
 6507
 6508    // rewrapping a single comment w/ abutting comments
 6509    assert_rewrap(
 6510        indoc! {"
 6511            /* ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6512            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6513        "},
 6514        indoc! {"
 6515            /*
 6516             * ˇLorem ipsum dolor sit amet,
 6517             * consectetur adipiscing elit.
 6518             */
 6519            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6520        "},
 6521        rust_lang.clone(),
 6522        &mut cx,
 6523    );
 6524
 6525    // selection w/ non-abutting short block comments
 6526    assert_rewrap(
 6527        indoc! {"
 6528            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6529
 6530            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6531        "},
 6532        indoc! {"
 6533            «/*
 6534             * Lorem ipsum dolor sit amet,
 6535             * consectetur adipiscing elit.
 6536             */
 6537
 6538            /*
 6539             * Lorem ipsum dolor sit amet,
 6540             * consectetur adipiscing elit.
 6541             */ˇ»
 6542        "},
 6543        rust_lang.clone(),
 6544        &mut cx,
 6545    );
 6546
 6547    // selection of multiline block comments
 6548    assert_rewrap(
 6549        indoc! {"
 6550            «/* Lorem ipsum dolor sit amet,
 6551             * consectetur adipiscing elit. */ˇ»
 6552        "},
 6553        indoc! {"
 6554            «/*
 6555             * Lorem ipsum dolor sit amet,
 6556             * consectetur adipiscing elit.
 6557             */ˇ»
 6558        "},
 6559        rust_lang.clone(),
 6560        &mut cx,
 6561    );
 6562
 6563    // partial selection of multiline block comments
 6564    assert_rewrap(
 6565        indoc! {"
 6566            «/* Lorem ipsum dolor sit amet,ˇ»
 6567             * consectetur adipiscing elit. */
 6568            /* Lorem ipsum dolor sit amet,
 6569             «* consectetur adipiscing elit. */ˇ»
 6570        "},
 6571        indoc! {"
 6572            «/*
 6573             * Lorem ipsum dolor sit amet,ˇ»
 6574             * consectetur adipiscing elit. */
 6575            /* Lorem ipsum dolor sit amet,
 6576             «* consectetur adipiscing elit.
 6577             */ˇ»
 6578        "},
 6579        rust_lang.clone(),
 6580        &mut cx,
 6581    );
 6582
 6583    // selection w/ abutting short block comments
 6584    // TODO: should not be combined; should rewrap as 2 comments
 6585    assert_rewrap(
 6586        indoc! {"
 6587            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6588            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6589        "},
 6590        // desired behavior:
 6591        // indoc! {"
 6592        //     «/*
 6593        //      * Lorem ipsum dolor sit amet,
 6594        //      * consectetur adipiscing elit.
 6595        //      */
 6596        //     /*
 6597        //      * Lorem ipsum dolor sit amet,
 6598        //      * consectetur adipiscing elit.
 6599        //      */ˇ»
 6600        // "},
 6601        // actual behaviour:
 6602        indoc! {"
 6603            «/*
 6604             * Lorem ipsum dolor sit amet,
 6605             * consectetur adipiscing elit. Lorem
 6606             * ipsum dolor sit amet, consectetur
 6607             * adipiscing elit.
 6608             */ˇ»
 6609        "},
 6610        rust_lang.clone(),
 6611        &mut cx,
 6612    );
 6613
 6614    // TODO: same as above, but with delimiters on separate line
 6615    // assert_rewrap(
 6616    //     indoc! {"
 6617    //         «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6618    //          */
 6619    //         /*
 6620    //          * Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6621    //     "},
 6622    //     // desired:
 6623    //     // indoc! {"
 6624    //     //     «/*
 6625    //     //      * Lorem ipsum dolor sit amet,
 6626    //     //      * consectetur adipiscing elit.
 6627    //     //      */
 6628    //     //     /*
 6629    //     //      * Lorem ipsum dolor sit amet,
 6630    //     //      * consectetur adipiscing elit.
 6631    //     //      */ˇ»
 6632    //     // "},
 6633    //     // actual: (but with trailing w/s on the empty lines)
 6634    //     indoc! {"
 6635    //         «/*
 6636    //          * Lorem ipsum dolor sit amet,
 6637    //          * consectetur adipiscing elit.
 6638    //          *
 6639    //          */
 6640    //         /*
 6641    //          *
 6642    //          * Lorem ipsum dolor sit amet,
 6643    //          * consectetur adipiscing elit.
 6644    //          */ˇ»
 6645    //     "},
 6646    //     rust_lang.clone(),
 6647    //     &mut cx,
 6648    // );
 6649
 6650    // TODO these are unhandled edge cases; not correct, just documenting known issues
 6651    assert_rewrap(
 6652        indoc! {"
 6653            /*
 6654             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6655             */
 6656            /*
 6657             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6658            /*ˇ Lorem ipsum dolor sit amet */ /* consectetur adipiscing elit. */
 6659        "},
 6660        // desired:
 6661        // indoc! {"
 6662        //     /*
 6663        //      *ˇ Lorem ipsum dolor sit amet,
 6664        //      * consectetur adipiscing elit.
 6665        //      */
 6666        //     /*
 6667        //      *ˇ Lorem ipsum dolor sit amet,
 6668        //      * consectetur adipiscing elit.
 6669        //      */
 6670        //     /*
 6671        //      *ˇ Lorem ipsum dolor sit amet
 6672        //      */ /* consectetur adipiscing elit. */
 6673        // "},
 6674        // actual:
 6675        indoc! {"
 6676            /*
 6677             //ˇ Lorem ipsum dolor sit amet,
 6678             // consectetur adipiscing elit.
 6679             */
 6680            /*
 6681             * //ˇ Lorem ipsum dolor sit amet,
 6682             * consectetur adipiscing elit.
 6683             */
 6684            /*
 6685             *ˇ Lorem ipsum dolor sit amet */ /*
 6686             * consectetur adipiscing elit.
 6687             */
 6688        "},
 6689        rust_lang,
 6690        &mut cx,
 6691    );
 6692
 6693    #[track_caller]
 6694    fn assert_rewrap(
 6695        unwrapped_text: &str,
 6696        wrapped_text: &str,
 6697        language: Arc<Language>,
 6698        cx: &mut EditorTestContext,
 6699    ) {
 6700        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6701        cx.set_state(unwrapped_text);
 6702        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6703        cx.assert_editor_state(wrapped_text);
 6704    }
 6705}
 6706
 6707#[gpui::test]
 6708async fn test_hard_wrap(cx: &mut TestAppContext) {
 6709    init_test(cx, |_| {});
 6710    let mut cx = EditorTestContext::new(cx).await;
 6711
 6712    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 6713    cx.update_editor(|editor, _, cx| {
 6714        editor.set_hard_wrap(Some(14), cx);
 6715    });
 6716
 6717    cx.set_state(indoc!(
 6718        "
 6719        one two three ˇ
 6720        "
 6721    ));
 6722    cx.simulate_input("four");
 6723    cx.run_until_parked();
 6724
 6725    cx.assert_editor_state(indoc!(
 6726        "
 6727        one two three
 6728        fourˇ
 6729        "
 6730    ));
 6731
 6732    cx.update_editor(|editor, window, cx| {
 6733        editor.newline(&Default::default(), window, cx);
 6734    });
 6735    cx.run_until_parked();
 6736    cx.assert_editor_state(indoc!(
 6737        "
 6738        one two three
 6739        four
 6740        ˇ
 6741        "
 6742    ));
 6743
 6744    cx.simulate_input("five");
 6745    cx.run_until_parked();
 6746    cx.assert_editor_state(indoc!(
 6747        "
 6748        one two three
 6749        four
 6750        fiveˇ
 6751        "
 6752    ));
 6753
 6754    cx.update_editor(|editor, window, cx| {
 6755        editor.newline(&Default::default(), window, cx);
 6756    });
 6757    cx.run_until_parked();
 6758    cx.simulate_input("# ");
 6759    cx.run_until_parked();
 6760    cx.assert_editor_state(indoc!(
 6761        "
 6762        one two three
 6763        four
 6764        five
 6765        # ˇ
 6766        "
 6767    ));
 6768
 6769    cx.update_editor(|editor, window, cx| {
 6770        editor.newline(&Default::default(), window, cx);
 6771    });
 6772    cx.run_until_parked();
 6773    cx.assert_editor_state(indoc!(
 6774        "
 6775        one two three
 6776        four
 6777        five
 6778        #\x20
 6779 6780        "
 6781    ));
 6782
 6783    cx.simulate_input(" 6");
 6784    cx.run_until_parked();
 6785    cx.assert_editor_state(indoc!(
 6786        "
 6787        one two three
 6788        four
 6789        five
 6790        #
 6791        # 6ˇ
 6792        "
 6793    ));
 6794}
 6795
 6796#[gpui::test]
 6797async fn test_cut_line_ends(cx: &mut TestAppContext) {
 6798    init_test(cx, |_| {});
 6799
 6800    let mut cx = EditorTestContext::new(cx).await;
 6801
 6802    cx.set_state(indoc! {"The quick brownˇ"});
 6803    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 6804    cx.assert_editor_state(indoc! {"The quick brownˇ"});
 6805
 6806    cx.set_state(indoc! {"The emacs foxˇ"});
 6807    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 6808    cx.assert_editor_state(indoc! {"The emacs foxˇ"});
 6809
 6810    cx.set_state(indoc! {"
 6811        The quick« brownˇ»
 6812        fox jumps overˇ
 6813        the lazy dog"});
 6814    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6815    cx.assert_editor_state(indoc! {"
 6816        The quickˇ
 6817        ˇthe lazy dog"});
 6818
 6819    cx.set_state(indoc! {"
 6820        The quick« brownˇ»
 6821        fox jumps overˇ
 6822        the lazy dog"});
 6823    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 6824    cx.assert_editor_state(indoc! {"
 6825        The quickˇ
 6826        fox jumps overˇthe lazy dog"});
 6827
 6828    cx.set_state(indoc! {"
 6829        The quick« brownˇ»
 6830        fox jumps overˇ
 6831        the lazy dog"});
 6832    cx.update_editor(|e, window, cx| {
 6833        e.cut_to_end_of_line(
 6834            &CutToEndOfLine {
 6835                stop_at_newlines: true,
 6836            },
 6837            window,
 6838            cx,
 6839        )
 6840    });
 6841    cx.assert_editor_state(indoc! {"
 6842        The quickˇ
 6843        fox jumps overˇ
 6844        the lazy dog"});
 6845
 6846    cx.set_state(indoc! {"
 6847        The quick« brownˇ»
 6848        fox jumps overˇ
 6849        the lazy dog"});
 6850    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 6851    cx.assert_editor_state(indoc! {"
 6852        The quickˇ
 6853        fox jumps overˇthe lazy dog"});
 6854}
 6855
 6856#[gpui::test]
 6857async fn test_clipboard(cx: &mut TestAppContext) {
 6858    init_test(cx, |_| {});
 6859
 6860    let mut cx = EditorTestContext::new(cx).await;
 6861
 6862    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 6863    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6864    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 6865
 6866    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 6867    cx.set_state("two ˇfour ˇsix ˇ");
 6868    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6869    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 6870
 6871    // Paste again but with only two cursors. Since the number of cursors doesn't
 6872    // match the number of slices in the clipboard, the entire clipboard text
 6873    // is pasted at each cursor.
 6874    cx.set_state("ˇtwo one✅ four three six five ˇ");
 6875    cx.update_editor(|e, window, cx| {
 6876        e.handle_input("( ", window, cx);
 6877        e.paste(&Paste, window, cx);
 6878        e.handle_input(") ", window, cx);
 6879    });
 6880    cx.assert_editor_state(
 6881        &([
 6882            "( one✅ ",
 6883            "three ",
 6884            "five ) ˇtwo one✅ four three six five ( one✅ ",
 6885            "three ",
 6886            "five ) ˇ",
 6887        ]
 6888        .join("\n")),
 6889    );
 6890
 6891    // Cut with three selections, one of which is full-line.
 6892    cx.set_state(indoc! {"
 6893        1«2ˇ»3
 6894        4ˇ567
 6895        «8ˇ»9"});
 6896    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6897    cx.assert_editor_state(indoc! {"
 6898        1ˇ3
 6899        ˇ9"});
 6900
 6901    // Paste with three selections, noticing how the copied selection that was full-line
 6902    // gets inserted before the second cursor.
 6903    cx.set_state(indoc! {"
 6904        1ˇ3
 6905 6906        «oˇ»ne"});
 6907    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6908    cx.assert_editor_state(indoc! {"
 6909        12ˇ3
 6910        4567
 6911 6912        8ˇne"});
 6913
 6914    // Copy with a single cursor only, which writes the whole line into the clipboard.
 6915    cx.set_state(indoc! {"
 6916        The quick brown
 6917        fox juˇmps over
 6918        the lazy dog"});
 6919    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6920    assert_eq!(
 6921        cx.read_from_clipboard()
 6922            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6923        Some("fox jumps over\n".to_string())
 6924    );
 6925
 6926    // Paste with three selections, noticing how the copied full-line selection is inserted
 6927    // before the empty selections but replaces the selection that is non-empty.
 6928    cx.set_state(indoc! {"
 6929        Tˇhe quick brown
 6930        «foˇ»x jumps over
 6931        tˇhe lazy dog"});
 6932    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6933    cx.assert_editor_state(indoc! {"
 6934        fox jumps over
 6935        Tˇhe quick brown
 6936        fox jumps over
 6937        ˇx jumps over
 6938        fox jumps over
 6939        tˇhe lazy dog"});
 6940}
 6941
 6942#[gpui::test]
 6943async fn test_copy_trim(cx: &mut TestAppContext) {
 6944    init_test(cx, |_| {});
 6945
 6946    let mut cx = EditorTestContext::new(cx).await;
 6947    cx.set_state(
 6948        r#"            «for selection in selections.iter() {
 6949            let mut start = selection.start;
 6950            let mut end = selection.end;
 6951            let is_entire_line = selection.is_empty();
 6952            if is_entire_line {
 6953                start = Point::new(start.row, 0);ˇ»
 6954                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6955            }
 6956        "#,
 6957    );
 6958    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6959    assert_eq!(
 6960        cx.read_from_clipboard()
 6961            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6962        Some(
 6963            "for selection in selections.iter() {
 6964            let mut start = selection.start;
 6965            let mut end = selection.end;
 6966            let is_entire_line = selection.is_empty();
 6967            if is_entire_line {
 6968                start = Point::new(start.row, 0);"
 6969                .to_string()
 6970        ),
 6971        "Regular copying preserves all indentation selected",
 6972    );
 6973    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6974    assert_eq!(
 6975        cx.read_from_clipboard()
 6976            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6977        Some(
 6978            "for selection in selections.iter() {
 6979let mut start = selection.start;
 6980let mut end = selection.end;
 6981let is_entire_line = selection.is_empty();
 6982if is_entire_line {
 6983    start = Point::new(start.row, 0);"
 6984                .to_string()
 6985        ),
 6986        "Copying with stripping should strip all leading whitespaces"
 6987    );
 6988
 6989    cx.set_state(
 6990        r#"       «     for selection in selections.iter() {
 6991            let mut start = selection.start;
 6992            let mut end = selection.end;
 6993            let is_entire_line = selection.is_empty();
 6994            if is_entire_line {
 6995                start = Point::new(start.row, 0);ˇ»
 6996                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6997            }
 6998        "#,
 6999    );
 7000    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7001    assert_eq!(
 7002        cx.read_from_clipboard()
 7003            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7004        Some(
 7005            "     for selection in selections.iter() {
 7006            let mut start = selection.start;
 7007            let mut end = selection.end;
 7008            let is_entire_line = selection.is_empty();
 7009            if is_entire_line {
 7010                start = Point::new(start.row, 0);"
 7011                .to_string()
 7012        ),
 7013        "Regular copying preserves all indentation selected",
 7014    );
 7015    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7016    assert_eq!(
 7017        cx.read_from_clipboard()
 7018            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7019        Some(
 7020            "for selection in selections.iter() {
 7021let mut start = selection.start;
 7022let mut end = selection.end;
 7023let is_entire_line = selection.is_empty();
 7024if is_entire_line {
 7025    start = Point::new(start.row, 0);"
 7026                .to_string()
 7027        ),
 7028        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 7029    );
 7030
 7031    cx.set_state(
 7032        r#"       «ˇ     for selection in selections.iter() {
 7033            let mut start = selection.start;
 7034            let mut end = selection.end;
 7035            let is_entire_line = selection.is_empty();
 7036            if is_entire_line {
 7037                start = Point::new(start.row, 0);»
 7038                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7039            }
 7040        "#,
 7041    );
 7042    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7043    assert_eq!(
 7044        cx.read_from_clipboard()
 7045            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7046        Some(
 7047            "     for selection in selections.iter() {
 7048            let mut start = selection.start;
 7049            let mut end = selection.end;
 7050            let is_entire_line = selection.is_empty();
 7051            if is_entire_line {
 7052                start = Point::new(start.row, 0);"
 7053                .to_string()
 7054        ),
 7055        "Regular copying for reverse selection works the same",
 7056    );
 7057    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7058    assert_eq!(
 7059        cx.read_from_clipboard()
 7060            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7061        Some(
 7062            "for selection in selections.iter() {
 7063let mut start = selection.start;
 7064let mut end = selection.end;
 7065let is_entire_line = selection.is_empty();
 7066if is_entire_line {
 7067    start = Point::new(start.row, 0);"
 7068                .to_string()
 7069        ),
 7070        "Copying with stripping for reverse selection works the same"
 7071    );
 7072
 7073    cx.set_state(
 7074        r#"            for selection «in selections.iter() {
 7075            let mut start = selection.start;
 7076            let mut end = selection.end;
 7077            let is_entire_line = selection.is_empty();
 7078            if is_entire_line {
 7079                start = Point::new(start.row, 0);ˇ»
 7080                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7081            }
 7082        "#,
 7083    );
 7084    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7085    assert_eq!(
 7086        cx.read_from_clipboard()
 7087            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7088        Some(
 7089            "in selections.iter() {
 7090            let mut start = selection.start;
 7091            let mut end = selection.end;
 7092            let is_entire_line = selection.is_empty();
 7093            if is_entire_line {
 7094                start = Point::new(start.row, 0);"
 7095                .to_string()
 7096        ),
 7097        "When selecting past the indent, the copying works as usual",
 7098    );
 7099    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7100    assert_eq!(
 7101        cx.read_from_clipboard()
 7102            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7103        Some(
 7104            "in selections.iter() {
 7105            let mut start = selection.start;
 7106            let mut end = selection.end;
 7107            let is_entire_line = selection.is_empty();
 7108            if is_entire_line {
 7109                start = Point::new(start.row, 0);"
 7110                .to_string()
 7111        ),
 7112        "When selecting past the indent, nothing is trimmed"
 7113    );
 7114
 7115    cx.set_state(
 7116        r#"            «for selection in selections.iter() {
 7117            let mut start = selection.start;
 7118
 7119            let mut end = selection.end;
 7120            let is_entire_line = selection.is_empty();
 7121            if is_entire_line {
 7122                start = Point::new(start.row, 0);
 7123ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7124            }
 7125        "#,
 7126    );
 7127    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7128    assert_eq!(
 7129        cx.read_from_clipboard()
 7130            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7131        Some(
 7132            "for selection in selections.iter() {
 7133let mut start = selection.start;
 7134
 7135let mut end = selection.end;
 7136let is_entire_line = selection.is_empty();
 7137if is_entire_line {
 7138    start = Point::new(start.row, 0);
 7139"
 7140            .to_string()
 7141        ),
 7142        "Copying with stripping should ignore empty lines"
 7143    );
 7144}
 7145
 7146#[gpui::test]
 7147async fn test_paste_multiline(cx: &mut TestAppContext) {
 7148    init_test(cx, |_| {});
 7149
 7150    let mut cx = EditorTestContext::new(cx).await;
 7151    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7152
 7153    // Cut an indented block, without the leading whitespace.
 7154    cx.set_state(indoc! {"
 7155        const a: B = (
 7156            c(),
 7157            «d(
 7158                e,
 7159                f
 7160            )ˇ»
 7161        );
 7162    "});
 7163    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7164    cx.assert_editor_state(indoc! {"
 7165        const a: B = (
 7166            c(),
 7167            ˇ
 7168        );
 7169    "});
 7170
 7171    // Paste it at the same position.
 7172    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7173    cx.assert_editor_state(indoc! {"
 7174        const a: B = (
 7175            c(),
 7176            d(
 7177                e,
 7178                f
 7179 7180        );
 7181    "});
 7182
 7183    // Paste it at a line with a lower indent level.
 7184    cx.set_state(indoc! {"
 7185        ˇ
 7186        const a: B = (
 7187            c(),
 7188        );
 7189    "});
 7190    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7191    cx.assert_editor_state(indoc! {"
 7192        d(
 7193            e,
 7194            f
 7195 7196        const a: B = (
 7197            c(),
 7198        );
 7199    "});
 7200
 7201    // Cut an indented block, with the leading whitespace.
 7202    cx.set_state(indoc! {"
 7203        const a: B = (
 7204            c(),
 7205        «    d(
 7206                e,
 7207                f
 7208            )
 7209        ˇ»);
 7210    "});
 7211    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7212    cx.assert_editor_state(indoc! {"
 7213        const a: B = (
 7214            c(),
 7215        ˇ);
 7216    "});
 7217
 7218    // Paste it at the same position.
 7219    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7220    cx.assert_editor_state(indoc! {"
 7221        const a: B = (
 7222            c(),
 7223            d(
 7224                e,
 7225                f
 7226            )
 7227        ˇ);
 7228    "});
 7229
 7230    // Paste it at a line with a higher indent level.
 7231    cx.set_state(indoc! {"
 7232        const a: B = (
 7233            c(),
 7234            d(
 7235                e,
 7236 7237            )
 7238        );
 7239    "});
 7240    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7241    cx.assert_editor_state(indoc! {"
 7242        const a: B = (
 7243            c(),
 7244            d(
 7245                e,
 7246                f    d(
 7247                    e,
 7248                    f
 7249                )
 7250        ˇ
 7251            )
 7252        );
 7253    "});
 7254
 7255    // Copy an indented block, starting mid-line
 7256    cx.set_state(indoc! {"
 7257        const a: B = (
 7258            c(),
 7259            somethin«g(
 7260                e,
 7261                f
 7262            )ˇ»
 7263        );
 7264    "});
 7265    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7266
 7267    // Paste it on a line with a lower indent level
 7268    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 7269    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7270    cx.assert_editor_state(indoc! {"
 7271        const a: B = (
 7272            c(),
 7273            something(
 7274                e,
 7275                f
 7276            )
 7277        );
 7278        g(
 7279            e,
 7280            f
 7281"});
 7282}
 7283
 7284#[gpui::test]
 7285async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 7286    init_test(cx, |_| {});
 7287
 7288    cx.write_to_clipboard(ClipboardItem::new_string(
 7289        "    d(\n        e\n    );\n".into(),
 7290    ));
 7291
 7292    let mut cx = EditorTestContext::new(cx).await;
 7293    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7294
 7295    cx.set_state(indoc! {"
 7296        fn a() {
 7297            b();
 7298            if c() {
 7299                ˇ
 7300            }
 7301        }
 7302    "});
 7303
 7304    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7305    cx.assert_editor_state(indoc! {"
 7306        fn a() {
 7307            b();
 7308            if c() {
 7309                d(
 7310                    e
 7311                );
 7312        ˇ
 7313            }
 7314        }
 7315    "});
 7316
 7317    cx.set_state(indoc! {"
 7318        fn a() {
 7319            b();
 7320            ˇ
 7321        }
 7322    "});
 7323
 7324    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7325    cx.assert_editor_state(indoc! {"
 7326        fn a() {
 7327            b();
 7328            d(
 7329                e
 7330            );
 7331        ˇ
 7332        }
 7333    "});
 7334}
 7335
 7336#[gpui::test]
 7337fn test_select_all(cx: &mut TestAppContext) {
 7338    init_test(cx, |_| {});
 7339
 7340    let editor = cx.add_window(|window, cx| {
 7341        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 7342        build_editor(buffer, window, cx)
 7343    });
 7344    _ = editor.update(cx, |editor, window, cx| {
 7345        editor.select_all(&SelectAll, window, cx);
 7346        assert_eq!(
 7347            editor.selections.display_ranges(cx),
 7348            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 7349        );
 7350    });
 7351}
 7352
 7353#[gpui::test]
 7354fn test_select_line(cx: &mut TestAppContext) {
 7355    init_test(cx, |_| {});
 7356
 7357    let editor = cx.add_window(|window, cx| {
 7358        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 7359        build_editor(buffer, window, cx)
 7360    });
 7361    _ = editor.update(cx, |editor, window, cx| {
 7362        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7363            s.select_display_ranges([
 7364                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7365                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7366                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7367                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 7368            ])
 7369        });
 7370        editor.select_line(&SelectLine, window, cx);
 7371        assert_eq!(
 7372            editor.selections.display_ranges(cx),
 7373            vec![
 7374                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 7375                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 7376            ]
 7377        );
 7378    });
 7379
 7380    _ = editor.update(cx, |editor, window, cx| {
 7381        editor.select_line(&SelectLine, window, cx);
 7382        assert_eq!(
 7383            editor.selections.display_ranges(cx),
 7384            vec![
 7385                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 7386                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 7387            ]
 7388        );
 7389    });
 7390
 7391    _ = editor.update(cx, |editor, window, cx| {
 7392        editor.select_line(&SelectLine, window, cx);
 7393        assert_eq!(
 7394            editor.selections.display_ranges(cx),
 7395            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 7396        );
 7397    });
 7398}
 7399
 7400#[gpui::test]
 7401async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 7402    init_test(cx, |_| {});
 7403    let mut cx = EditorTestContext::new(cx).await;
 7404
 7405    #[track_caller]
 7406    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 7407        cx.set_state(initial_state);
 7408        cx.update_editor(|e, window, cx| {
 7409            e.split_selection_into_lines(&Default::default(), window, cx)
 7410        });
 7411        cx.assert_editor_state(expected_state);
 7412    }
 7413
 7414    // Selection starts and ends at the middle of lines, left-to-right
 7415    test(
 7416        &mut cx,
 7417        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 7418        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7419    );
 7420    // Same thing, right-to-left
 7421    test(
 7422        &mut cx,
 7423        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 7424        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7425    );
 7426
 7427    // Whole buffer, left-to-right, last line *doesn't* end with newline
 7428    test(
 7429        &mut cx,
 7430        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 7431        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7432    );
 7433    // Same thing, right-to-left
 7434    test(
 7435        &mut cx,
 7436        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 7437        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7438    );
 7439
 7440    // Whole buffer, left-to-right, last line ends with newline
 7441    test(
 7442        &mut cx,
 7443        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 7444        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7445    );
 7446    // Same thing, right-to-left
 7447    test(
 7448        &mut cx,
 7449        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 7450        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7451    );
 7452
 7453    // Starts at the end of a line, ends at the start of another
 7454    test(
 7455        &mut cx,
 7456        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 7457        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 7458    );
 7459}
 7460
 7461#[gpui::test]
 7462async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 7463    init_test(cx, |_| {});
 7464
 7465    let editor = cx.add_window(|window, cx| {
 7466        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 7467        build_editor(buffer, window, cx)
 7468    });
 7469
 7470    // setup
 7471    _ = editor.update(cx, |editor, window, cx| {
 7472        editor.fold_creases(
 7473            vec![
 7474                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 7475                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 7476                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 7477            ],
 7478            true,
 7479            window,
 7480            cx,
 7481        );
 7482        assert_eq!(
 7483            editor.display_text(cx),
 7484            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7485        );
 7486    });
 7487
 7488    _ = editor.update(cx, |editor, window, cx| {
 7489        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7490            s.select_display_ranges([
 7491                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7492                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7493                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7494                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 7495            ])
 7496        });
 7497        editor.split_selection_into_lines(&Default::default(), window, cx);
 7498        assert_eq!(
 7499            editor.display_text(cx),
 7500            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7501        );
 7502    });
 7503    EditorTestContext::for_editor(editor, cx)
 7504        .await
 7505        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 7506
 7507    _ = editor.update(cx, |editor, window, cx| {
 7508        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7509            s.select_display_ranges([
 7510                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 7511            ])
 7512        });
 7513        editor.split_selection_into_lines(&Default::default(), window, cx);
 7514        assert_eq!(
 7515            editor.display_text(cx),
 7516            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 7517        );
 7518        assert_eq!(
 7519            editor.selections.display_ranges(cx),
 7520            [
 7521                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 7522                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 7523                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 7524                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 7525                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 7526                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 7527                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 7528            ]
 7529        );
 7530    });
 7531    EditorTestContext::for_editor(editor, cx)
 7532        .await
 7533        .assert_editor_state(
 7534            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 7535        );
 7536}
 7537
 7538#[gpui::test]
 7539async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 7540    init_test(cx, |_| {});
 7541
 7542    let mut cx = EditorTestContext::new(cx).await;
 7543
 7544    cx.set_state(indoc!(
 7545        r#"abc
 7546           defˇghi
 7547
 7548           jk
 7549           nlmo
 7550           "#
 7551    ));
 7552
 7553    cx.update_editor(|editor, window, cx| {
 7554        editor.add_selection_above(&Default::default(), window, cx);
 7555    });
 7556
 7557    cx.assert_editor_state(indoc!(
 7558        r#"abcˇ
 7559           defˇghi
 7560
 7561           jk
 7562           nlmo
 7563           "#
 7564    ));
 7565
 7566    cx.update_editor(|editor, window, cx| {
 7567        editor.add_selection_above(&Default::default(), window, cx);
 7568    });
 7569
 7570    cx.assert_editor_state(indoc!(
 7571        r#"abcˇ
 7572            defˇghi
 7573
 7574            jk
 7575            nlmo
 7576            "#
 7577    ));
 7578
 7579    cx.update_editor(|editor, window, cx| {
 7580        editor.add_selection_below(&Default::default(), window, cx);
 7581    });
 7582
 7583    cx.assert_editor_state(indoc!(
 7584        r#"abc
 7585           defˇghi
 7586
 7587           jk
 7588           nlmo
 7589           "#
 7590    ));
 7591
 7592    cx.update_editor(|editor, window, cx| {
 7593        editor.undo_selection(&Default::default(), window, cx);
 7594    });
 7595
 7596    cx.assert_editor_state(indoc!(
 7597        r#"abcˇ
 7598           defˇghi
 7599
 7600           jk
 7601           nlmo
 7602           "#
 7603    ));
 7604
 7605    cx.update_editor(|editor, window, cx| {
 7606        editor.redo_selection(&Default::default(), window, cx);
 7607    });
 7608
 7609    cx.assert_editor_state(indoc!(
 7610        r#"abc
 7611           defˇghi
 7612
 7613           jk
 7614           nlmo
 7615           "#
 7616    ));
 7617
 7618    cx.update_editor(|editor, window, cx| {
 7619        editor.add_selection_below(&Default::default(), window, cx);
 7620    });
 7621
 7622    cx.assert_editor_state(indoc!(
 7623        r#"abc
 7624           defˇghi
 7625           ˇ
 7626           jk
 7627           nlmo
 7628           "#
 7629    ));
 7630
 7631    cx.update_editor(|editor, window, cx| {
 7632        editor.add_selection_below(&Default::default(), window, cx);
 7633    });
 7634
 7635    cx.assert_editor_state(indoc!(
 7636        r#"abc
 7637           defˇghi
 7638           ˇ
 7639           jkˇ
 7640           nlmo
 7641           "#
 7642    ));
 7643
 7644    cx.update_editor(|editor, window, cx| {
 7645        editor.add_selection_below(&Default::default(), window, cx);
 7646    });
 7647
 7648    cx.assert_editor_state(indoc!(
 7649        r#"abc
 7650           defˇghi
 7651           ˇ
 7652           jkˇ
 7653           nlmˇo
 7654           "#
 7655    ));
 7656
 7657    cx.update_editor(|editor, window, cx| {
 7658        editor.add_selection_below(&Default::default(), window, cx);
 7659    });
 7660
 7661    cx.assert_editor_state(indoc!(
 7662        r#"abc
 7663           defˇghi
 7664           ˇ
 7665           jkˇ
 7666           nlmˇo
 7667           ˇ"#
 7668    ));
 7669
 7670    // change selections
 7671    cx.set_state(indoc!(
 7672        r#"abc
 7673           def«ˇg»hi
 7674
 7675           jk
 7676           nlmo
 7677           "#
 7678    ));
 7679
 7680    cx.update_editor(|editor, window, cx| {
 7681        editor.add_selection_below(&Default::default(), window, cx);
 7682    });
 7683
 7684    cx.assert_editor_state(indoc!(
 7685        r#"abc
 7686           def«ˇg»hi
 7687
 7688           jk
 7689           nlm«ˇo»
 7690           "#
 7691    ));
 7692
 7693    cx.update_editor(|editor, window, cx| {
 7694        editor.add_selection_below(&Default::default(), window, cx);
 7695    });
 7696
 7697    cx.assert_editor_state(indoc!(
 7698        r#"abc
 7699           def«ˇg»hi
 7700
 7701           jk
 7702           nlm«ˇo»
 7703           "#
 7704    ));
 7705
 7706    cx.update_editor(|editor, window, cx| {
 7707        editor.add_selection_above(&Default::default(), window, cx);
 7708    });
 7709
 7710    cx.assert_editor_state(indoc!(
 7711        r#"abc
 7712           def«ˇg»hi
 7713
 7714           jk
 7715           nlmo
 7716           "#
 7717    ));
 7718
 7719    cx.update_editor(|editor, window, cx| {
 7720        editor.add_selection_above(&Default::default(), window, cx);
 7721    });
 7722
 7723    cx.assert_editor_state(indoc!(
 7724        r#"abc
 7725           def«ˇg»hi
 7726
 7727           jk
 7728           nlmo
 7729           "#
 7730    ));
 7731
 7732    // Change selections again
 7733    cx.set_state(indoc!(
 7734        r#"a«bc
 7735           defgˇ»hi
 7736
 7737           jk
 7738           nlmo
 7739           "#
 7740    ));
 7741
 7742    cx.update_editor(|editor, window, cx| {
 7743        editor.add_selection_below(&Default::default(), window, cx);
 7744    });
 7745
 7746    cx.assert_editor_state(indoc!(
 7747        r#"a«bcˇ»
 7748           d«efgˇ»hi
 7749
 7750           j«kˇ»
 7751           nlmo
 7752           "#
 7753    ));
 7754
 7755    cx.update_editor(|editor, window, cx| {
 7756        editor.add_selection_below(&Default::default(), window, cx);
 7757    });
 7758    cx.assert_editor_state(indoc!(
 7759        r#"a«bcˇ»
 7760           d«efgˇ»hi
 7761
 7762           j«kˇ»
 7763           n«lmoˇ»
 7764           "#
 7765    ));
 7766    cx.update_editor(|editor, window, cx| {
 7767        editor.add_selection_above(&Default::default(), window, cx);
 7768    });
 7769
 7770    cx.assert_editor_state(indoc!(
 7771        r#"a«bcˇ»
 7772           d«efgˇ»hi
 7773
 7774           j«kˇ»
 7775           nlmo
 7776           "#
 7777    ));
 7778
 7779    // Change selections again
 7780    cx.set_state(indoc!(
 7781        r#"abc
 7782           d«ˇefghi
 7783
 7784           jk
 7785           nlm»o
 7786           "#
 7787    ));
 7788
 7789    cx.update_editor(|editor, window, cx| {
 7790        editor.add_selection_above(&Default::default(), window, cx);
 7791    });
 7792
 7793    cx.assert_editor_state(indoc!(
 7794        r#"a«ˇbc»
 7795           d«ˇef»ghi
 7796
 7797           j«ˇk»
 7798           n«ˇlm»o
 7799           "#
 7800    ));
 7801
 7802    cx.update_editor(|editor, window, cx| {
 7803        editor.add_selection_below(&Default::default(), window, cx);
 7804    });
 7805
 7806    cx.assert_editor_state(indoc!(
 7807        r#"abc
 7808           d«ˇef»ghi
 7809
 7810           j«ˇk»
 7811           n«ˇlm»o
 7812           "#
 7813    ));
 7814}
 7815
 7816#[gpui::test]
 7817async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 7818    init_test(cx, |_| {});
 7819    let mut cx = EditorTestContext::new(cx).await;
 7820
 7821    cx.set_state(indoc!(
 7822        r#"line onˇe
 7823           liˇne two
 7824           line three
 7825           line four"#
 7826    ));
 7827
 7828    cx.update_editor(|editor, window, cx| {
 7829        editor.add_selection_below(&Default::default(), window, cx);
 7830    });
 7831
 7832    // test multiple cursors expand in the same direction
 7833    cx.assert_editor_state(indoc!(
 7834        r#"line onˇe
 7835           liˇne twˇo
 7836           liˇne three
 7837           line four"#
 7838    ));
 7839
 7840    cx.update_editor(|editor, window, cx| {
 7841        editor.add_selection_below(&Default::default(), window, cx);
 7842    });
 7843
 7844    cx.update_editor(|editor, window, cx| {
 7845        editor.add_selection_below(&Default::default(), window, cx);
 7846    });
 7847
 7848    // test multiple cursors expand below overflow
 7849    cx.assert_editor_state(indoc!(
 7850        r#"line onˇe
 7851           liˇne twˇo
 7852           liˇne thˇree
 7853           liˇne foˇur"#
 7854    ));
 7855
 7856    cx.update_editor(|editor, window, cx| {
 7857        editor.add_selection_above(&Default::default(), window, cx);
 7858    });
 7859
 7860    // test multiple cursors retrieves back correctly
 7861    cx.assert_editor_state(indoc!(
 7862        r#"line onˇe
 7863           liˇne twˇo
 7864           liˇne thˇree
 7865           line four"#
 7866    ));
 7867
 7868    cx.update_editor(|editor, window, cx| {
 7869        editor.add_selection_above(&Default::default(), window, cx);
 7870    });
 7871
 7872    cx.update_editor(|editor, window, cx| {
 7873        editor.add_selection_above(&Default::default(), window, cx);
 7874    });
 7875
 7876    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 7877    cx.assert_editor_state(indoc!(
 7878        r#"liˇne onˇe
 7879           liˇne two
 7880           line three
 7881           line four"#
 7882    ));
 7883
 7884    cx.update_editor(|editor, window, cx| {
 7885        editor.undo_selection(&Default::default(), window, cx);
 7886    });
 7887
 7888    // test undo
 7889    cx.assert_editor_state(indoc!(
 7890        r#"line onˇe
 7891           liˇne twˇo
 7892           line three
 7893           line four"#
 7894    ));
 7895
 7896    cx.update_editor(|editor, window, cx| {
 7897        editor.redo_selection(&Default::default(), window, cx);
 7898    });
 7899
 7900    // test redo
 7901    cx.assert_editor_state(indoc!(
 7902        r#"liˇne onˇe
 7903           liˇne two
 7904           line three
 7905           line four"#
 7906    ));
 7907
 7908    cx.set_state(indoc!(
 7909        r#"abcd
 7910           ef«ghˇ»
 7911           ijkl
 7912           «mˇ»nop"#
 7913    ));
 7914
 7915    cx.update_editor(|editor, window, cx| {
 7916        editor.add_selection_above(&Default::default(), window, cx);
 7917    });
 7918
 7919    // test multiple selections expand in the same direction
 7920    cx.assert_editor_state(indoc!(
 7921        r#"ab«cdˇ»
 7922           ef«ghˇ»
 7923           «iˇ»jkl
 7924           «mˇ»nop"#
 7925    ));
 7926
 7927    cx.update_editor(|editor, window, cx| {
 7928        editor.add_selection_above(&Default::default(), window, cx);
 7929    });
 7930
 7931    // test multiple selection upward overflow
 7932    cx.assert_editor_state(indoc!(
 7933        r#"ab«cdˇ»
 7934           «eˇ»f«ghˇ»
 7935           «iˇ»jkl
 7936           «mˇ»nop"#
 7937    ));
 7938
 7939    cx.update_editor(|editor, window, cx| {
 7940        editor.add_selection_below(&Default::default(), window, cx);
 7941    });
 7942
 7943    // test multiple selection retrieves back correctly
 7944    cx.assert_editor_state(indoc!(
 7945        r#"abcd
 7946           ef«ghˇ»
 7947           «iˇ»jkl
 7948           «mˇ»nop"#
 7949    ));
 7950
 7951    cx.update_editor(|editor, window, cx| {
 7952        editor.add_selection_below(&Default::default(), window, cx);
 7953    });
 7954
 7955    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 7956    cx.assert_editor_state(indoc!(
 7957        r#"abcd
 7958           ef«ghˇ»
 7959           ij«klˇ»
 7960           «mˇ»nop"#
 7961    ));
 7962
 7963    cx.update_editor(|editor, window, cx| {
 7964        editor.undo_selection(&Default::default(), window, cx);
 7965    });
 7966
 7967    // test undo
 7968    cx.assert_editor_state(indoc!(
 7969        r#"abcd
 7970           ef«ghˇ»
 7971           «iˇ»jkl
 7972           «mˇ»nop"#
 7973    ));
 7974
 7975    cx.update_editor(|editor, window, cx| {
 7976        editor.redo_selection(&Default::default(), window, cx);
 7977    });
 7978
 7979    // test redo
 7980    cx.assert_editor_state(indoc!(
 7981        r#"abcd
 7982           ef«ghˇ»
 7983           ij«klˇ»
 7984           «mˇ»nop"#
 7985    ));
 7986}
 7987
 7988#[gpui::test]
 7989async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 7990    init_test(cx, |_| {});
 7991    let mut cx = EditorTestContext::new(cx).await;
 7992
 7993    cx.set_state(indoc!(
 7994        r#"line onˇe
 7995           liˇne two
 7996           line three
 7997           line four"#
 7998    ));
 7999
 8000    cx.update_editor(|editor, window, cx| {
 8001        editor.add_selection_below(&Default::default(), window, cx);
 8002        editor.add_selection_below(&Default::default(), window, cx);
 8003        editor.add_selection_below(&Default::default(), window, cx);
 8004    });
 8005
 8006    // initial state with two multi cursor groups
 8007    cx.assert_editor_state(indoc!(
 8008        r#"line onˇe
 8009           liˇne twˇo
 8010           liˇne thˇree
 8011           liˇne foˇur"#
 8012    ));
 8013
 8014    // add single cursor in middle - simulate opt click
 8015    cx.update_editor(|editor, window, cx| {
 8016        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 8017        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 8018        editor.end_selection(window, cx);
 8019    });
 8020
 8021    cx.assert_editor_state(indoc!(
 8022        r#"line onˇe
 8023           liˇne twˇo
 8024           liˇneˇ thˇree
 8025           liˇne foˇur"#
 8026    ));
 8027
 8028    cx.update_editor(|editor, window, cx| {
 8029        editor.add_selection_above(&Default::default(), window, cx);
 8030    });
 8031
 8032    // test new added selection expands above and existing selection shrinks
 8033    cx.assert_editor_state(indoc!(
 8034        r#"line onˇe
 8035           liˇneˇ twˇo
 8036           liˇneˇ thˇree
 8037           line four"#
 8038    ));
 8039
 8040    cx.update_editor(|editor, window, cx| {
 8041        editor.add_selection_above(&Default::default(), window, cx);
 8042    });
 8043
 8044    // test new added selection expands above and existing selection shrinks
 8045    cx.assert_editor_state(indoc!(
 8046        r#"lineˇ onˇe
 8047           liˇneˇ twˇo
 8048           lineˇ three
 8049           line four"#
 8050    ));
 8051
 8052    // intial state with two selection groups
 8053    cx.set_state(indoc!(
 8054        r#"abcd
 8055           ef«ghˇ»
 8056           ijkl
 8057           «mˇ»nop"#
 8058    ));
 8059
 8060    cx.update_editor(|editor, window, cx| {
 8061        editor.add_selection_above(&Default::default(), window, cx);
 8062        editor.add_selection_above(&Default::default(), window, cx);
 8063    });
 8064
 8065    cx.assert_editor_state(indoc!(
 8066        r#"ab«cdˇ»
 8067           «eˇ»f«ghˇ»
 8068           «iˇ»jkl
 8069           «mˇ»nop"#
 8070    ));
 8071
 8072    // add single selection in middle - simulate opt drag
 8073    cx.update_editor(|editor, window, cx| {
 8074        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 8075        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 8076        editor.update_selection(
 8077            DisplayPoint::new(DisplayRow(2), 4),
 8078            0,
 8079            gpui::Point::<f32>::default(),
 8080            window,
 8081            cx,
 8082        );
 8083        editor.end_selection(window, cx);
 8084    });
 8085
 8086    cx.assert_editor_state(indoc!(
 8087        r#"ab«cdˇ»
 8088           «eˇ»f«ghˇ»
 8089           «iˇ»jk«lˇ»
 8090           «mˇ»nop"#
 8091    ));
 8092
 8093    cx.update_editor(|editor, window, cx| {
 8094        editor.add_selection_below(&Default::default(), window, cx);
 8095    });
 8096
 8097    // test new added selection expands below, others shrinks from above
 8098    cx.assert_editor_state(indoc!(
 8099        r#"abcd
 8100           ef«ghˇ»
 8101           «iˇ»jk«lˇ»
 8102           «mˇ»no«pˇ»"#
 8103    ));
 8104}
 8105
 8106#[gpui::test]
 8107async fn test_select_next(cx: &mut TestAppContext) {
 8108    init_test(cx, |_| {});
 8109
 8110    let mut cx = EditorTestContext::new(cx).await;
 8111    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8112
 8113    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8114        .unwrap();
 8115    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8116
 8117    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8118        .unwrap();
 8119    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8120
 8121    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8122    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8123
 8124    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8125    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8126
 8127    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8128        .unwrap();
 8129    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8130
 8131    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8132        .unwrap();
 8133    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8134
 8135    // Test selection direction should be preserved
 8136    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8137
 8138    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8139        .unwrap();
 8140    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 8141}
 8142
 8143#[gpui::test]
 8144async fn test_select_all_matches(cx: &mut TestAppContext) {
 8145    init_test(cx, |_| {});
 8146
 8147    let mut cx = EditorTestContext::new(cx).await;
 8148
 8149    // Test caret-only selections
 8150    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8151    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8152        .unwrap();
 8153    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8154
 8155    // Test left-to-right selections
 8156    cx.set_state("abc\n«abcˇ»\nabc");
 8157    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8158        .unwrap();
 8159    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 8160
 8161    // Test right-to-left selections
 8162    cx.set_state("abc\n«ˇabc»\nabc");
 8163    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8164        .unwrap();
 8165    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 8166
 8167    // Test selecting whitespace with caret selection
 8168    cx.set_state("abc\nˇ   abc\nabc");
 8169    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8170        .unwrap();
 8171    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 8172
 8173    // Test selecting whitespace with left-to-right selection
 8174    cx.set_state("abc\n«ˇ  »abc\nabc");
 8175    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8176        .unwrap();
 8177    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 8178
 8179    // Test no matches with right-to-left selection
 8180    cx.set_state("abc\n«  ˇ»abc\nabc");
 8181    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8182        .unwrap();
 8183    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 8184
 8185    // Test with a single word and clip_at_line_ends=true (#29823)
 8186    cx.set_state("aˇbc");
 8187    cx.update_editor(|e, window, cx| {
 8188        e.set_clip_at_line_ends(true, cx);
 8189        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8190        e.set_clip_at_line_ends(false, cx);
 8191    });
 8192    cx.assert_editor_state("«abcˇ»");
 8193}
 8194
 8195#[gpui::test]
 8196async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 8197    init_test(cx, |_| {});
 8198
 8199    let mut cx = EditorTestContext::new(cx).await;
 8200
 8201    let large_body_1 = "\nd".repeat(200);
 8202    let large_body_2 = "\ne".repeat(200);
 8203
 8204    cx.set_state(&format!(
 8205        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 8206    ));
 8207    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 8208        let scroll_position = editor.scroll_position(cx);
 8209        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 8210        scroll_position
 8211    });
 8212
 8213    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8214        .unwrap();
 8215    cx.assert_editor_state(&format!(
 8216        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 8217    ));
 8218    let scroll_position_after_selection =
 8219        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 8220    assert_eq!(
 8221        initial_scroll_position, scroll_position_after_selection,
 8222        "Scroll position should not change after selecting all matches"
 8223    );
 8224}
 8225
 8226#[gpui::test]
 8227async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 8228    init_test(cx, |_| {});
 8229
 8230    let mut cx = EditorLspTestContext::new_rust(
 8231        lsp::ServerCapabilities {
 8232            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8233            ..Default::default()
 8234        },
 8235        cx,
 8236    )
 8237    .await;
 8238
 8239    cx.set_state(indoc! {"
 8240        line 1
 8241        line 2
 8242        linˇe 3
 8243        line 4
 8244        line 5
 8245    "});
 8246
 8247    // Make an edit
 8248    cx.update_editor(|editor, window, cx| {
 8249        editor.handle_input("X", window, cx);
 8250    });
 8251
 8252    // Move cursor to a different position
 8253    cx.update_editor(|editor, window, cx| {
 8254        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8255            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 8256        });
 8257    });
 8258
 8259    cx.assert_editor_state(indoc! {"
 8260        line 1
 8261        line 2
 8262        linXe 3
 8263        line 4
 8264        liˇne 5
 8265    "});
 8266
 8267    cx.lsp
 8268        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 8269            Ok(Some(vec![lsp::TextEdit::new(
 8270                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 8271                "PREFIX ".to_string(),
 8272            )]))
 8273        });
 8274
 8275    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 8276        .unwrap()
 8277        .await
 8278        .unwrap();
 8279
 8280    cx.assert_editor_state(indoc! {"
 8281        PREFIX line 1
 8282        line 2
 8283        linXe 3
 8284        line 4
 8285        liˇne 5
 8286    "});
 8287
 8288    // Undo formatting
 8289    cx.update_editor(|editor, window, cx| {
 8290        editor.undo(&Default::default(), window, cx);
 8291    });
 8292
 8293    // Verify cursor moved back to position after edit
 8294    cx.assert_editor_state(indoc! {"
 8295        line 1
 8296        line 2
 8297        linXˇe 3
 8298        line 4
 8299        line 5
 8300    "});
 8301}
 8302
 8303#[gpui::test]
 8304async fn test_undo_edit_prediction_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 8305    init_test(cx, |_| {});
 8306
 8307    let mut cx = EditorTestContext::new(cx).await;
 8308
 8309    let provider = cx.new(|_| FakeEditPredictionProvider::default());
 8310    cx.update_editor(|editor, window, cx| {
 8311        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 8312    });
 8313
 8314    cx.set_state(indoc! {"
 8315        line 1
 8316        line 2
 8317        linˇe 3
 8318        line 4
 8319        line 5
 8320        line 6
 8321        line 7
 8322        line 8
 8323        line 9
 8324        line 10
 8325    "});
 8326
 8327    let snapshot = cx.buffer_snapshot();
 8328    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 8329
 8330    cx.update(|_, cx| {
 8331        provider.update(cx, |provider, _| {
 8332            provider.set_edit_prediction(Some(edit_prediction::EditPrediction::Local {
 8333                id: None,
 8334                edits: vec![(edit_position..edit_position, "X".into())],
 8335                edit_preview: None,
 8336            }))
 8337        })
 8338    });
 8339
 8340    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
 8341    cx.update_editor(|editor, window, cx| {
 8342        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 8343    });
 8344
 8345    cx.assert_editor_state(indoc! {"
 8346        line 1
 8347        line 2
 8348        lineXˇ 3
 8349        line 4
 8350        line 5
 8351        line 6
 8352        line 7
 8353        line 8
 8354        line 9
 8355        line 10
 8356    "});
 8357
 8358    cx.update_editor(|editor, window, cx| {
 8359        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8360            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 8361        });
 8362    });
 8363
 8364    cx.assert_editor_state(indoc! {"
 8365        line 1
 8366        line 2
 8367        lineX 3
 8368        line 4
 8369        line 5
 8370        line 6
 8371        line 7
 8372        line 8
 8373        line 9
 8374        liˇne 10
 8375    "});
 8376
 8377    cx.update_editor(|editor, window, cx| {
 8378        editor.undo(&Default::default(), window, cx);
 8379    });
 8380
 8381    cx.assert_editor_state(indoc! {"
 8382        line 1
 8383        line 2
 8384        lineˇ 3
 8385        line 4
 8386        line 5
 8387        line 6
 8388        line 7
 8389        line 8
 8390        line 9
 8391        line 10
 8392    "});
 8393}
 8394
 8395#[gpui::test]
 8396async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 8397    init_test(cx, |_| {});
 8398
 8399    let mut cx = EditorTestContext::new(cx).await;
 8400    cx.set_state(
 8401        r#"let foo = 2;
 8402lˇet foo = 2;
 8403let fooˇ = 2;
 8404let foo = 2;
 8405let foo = ˇ2;"#,
 8406    );
 8407
 8408    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8409        .unwrap();
 8410    cx.assert_editor_state(
 8411        r#"let foo = 2;
 8412«letˇ» foo = 2;
 8413let «fooˇ» = 2;
 8414let foo = 2;
 8415let foo = «2ˇ»;"#,
 8416    );
 8417
 8418    // noop for multiple selections with different contents
 8419    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8420        .unwrap();
 8421    cx.assert_editor_state(
 8422        r#"let foo = 2;
 8423«letˇ» foo = 2;
 8424let «fooˇ» = 2;
 8425let foo = 2;
 8426let foo = «2ˇ»;"#,
 8427    );
 8428
 8429    // Test last selection direction should be preserved
 8430    cx.set_state(
 8431        r#"let foo = 2;
 8432let foo = 2;
 8433let «fooˇ» = 2;
 8434let «ˇfoo» = 2;
 8435let foo = 2;"#,
 8436    );
 8437
 8438    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8439        .unwrap();
 8440    cx.assert_editor_state(
 8441        r#"let foo = 2;
 8442let foo = 2;
 8443let «fooˇ» = 2;
 8444let «ˇfoo» = 2;
 8445let «ˇfoo» = 2;"#,
 8446    );
 8447}
 8448
 8449#[gpui::test]
 8450async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 8451    init_test(cx, |_| {});
 8452
 8453    let mut cx =
 8454        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 8455
 8456    cx.assert_editor_state(indoc! {"
 8457        ˇbbb
 8458        ccc
 8459
 8460        bbb
 8461        ccc
 8462        "});
 8463    cx.dispatch_action(SelectPrevious::default());
 8464    cx.assert_editor_state(indoc! {"
 8465                «bbbˇ»
 8466                ccc
 8467
 8468                bbb
 8469                ccc
 8470                "});
 8471    cx.dispatch_action(SelectPrevious::default());
 8472    cx.assert_editor_state(indoc! {"
 8473                «bbbˇ»
 8474                ccc
 8475
 8476                «bbbˇ»
 8477                ccc
 8478                "});
 8479}
 8480
 8481#[gpui::test]
 8482async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 8483    init_test(cx, |_| {});
 8484
 8485    let mut cx = EditorTestContext::new(cx).await;
 8486    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8487
 8488    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8489        .unwrap();
 8490    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8491
 8492    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8493        .unwrap();
 8494    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8495
 8496    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8497    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8498
 8499    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 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(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8507        .unwrap();
 8508    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8509}
 8510
 8511#[gpui::test]
 8512async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 8513    init_test(cx, |_| {});
 8514
 8515    let mut cx = EditorTestContext::new(cx).await;
 8516    cx.set_state("");
 8517
 8518    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8519        .unwrap();
 8520    cx.assert_editor_state("«aˇ»");
 8521    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8522        .unwrap();
 8523    cx.assert_editor_state("«aˇ»");
 8524}
 8525
 8526#[gpui::test]
 8527async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 8528    init_test(cx, |_| {});
 8529
 8530    let mut cx = EditorTestContext::new(cx).await;
 8531    cx.set_state(
 8532        r#"let foo = 2;
 8533lˇet foo = 2;
 8534let fooˇ = 2;
 8535let foo = 2;
 8536let foo = ˇ2;"#,
 8537    );
 8538
 8539    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8540        .unwrap();
 8541    cx.assert_editor_state(
 8542        r#"let foo = 2;
 8543«letˇ» foo = 2;
 8544let «fooˇ» = 2;
 8545let foo = 2;
 8546let foo = «2ˇ»;"#,
 8547    );
 8548
 8549    // noop for multiple selections with different contents
 8550    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8551        .unwrap();
 8552    cx.assert_editor_state(
 8553        r#"let foo = 2;
 8554«letˇ» foo = 2;
 8555let «fooˇ» = 2;
 8556let foo = 2;
 8557let foo = «2ˇ»;"#,
 8558    );
 8559}
 8560
 8561#[gpui::test]
 8562async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 8563    init_test(cx, |_| {});
 8564
 8565    let mut cx = EditorTestContext::new(cx).await;
 8566    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8567
 8568    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8569        .unwrap();
 8570    // selection direction is preserved
 8571    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 8572
 8573    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8574        .unwrap();
 8575    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 8576
 8577    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8578    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 8579
 8580    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8581    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 8582
 8583    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8584        .unwrap();
 8585    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 8586
 8587    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8588        .unwrap();
 8589    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 8590}
 8591
 8592#[gpui::test]
 8593async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 8594    init_test(cx, |_| {});
 8595
 8596    let language = Arc::new(Language::new(
 8597        LanguageConfig::default(),
 8598        Some(tree_sitter_rust::LANGUAGE.into()),
 8599    ));
 8600
 8601    let text = r#"
 8602        use mod1::mod2::{mod3, mod4};
 8603
 8604        fn fn_1(param1: bool, param2: &str) {
 8605            let var1 = "text";
 8606        }
 8607    "#
 8608    .unindent();
 8609
 8610    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8611    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8612    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8613
 8614    editor
 8615        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8616        .await;
 8617
 8618    editor.update_in(cx, |editor, window, cx| {
 8619        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8620            s.select_display_ranges([
 8621                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 8622                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 8623                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 8624            ]);
 8625        });
 8626        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8627    });
 8628    editor.update(cx, |editor, cx| {
 8629        assert_text_with_selections(
 8630            editor,
 8631            indoc! {r#"
 8632                use mod1::mod2::{mod3, «mod4ˇ»};
 8633
 8634                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8635                    let var1 = "«ˇtext»";
 8636                }
 8637            "#},
 8638            cx,
 8639        );
 8640    });
 8641
 8642    editor.update_in(cx, |editor, window, cx| {
 8643        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8644    });
 8645    editor.update(cx, |editor, cx| {
 8646        assert_text_with_selections(
 8647            editor,
 8648            indoc! {r#"
 8649                use mod1::mod2::«{mod3, mod4}ˇ»;
 8650
 8651                «ˇfn fn_1(param1: bool, param2: &str) {
 8652                    let var1 = "text";
 8653 8654            "#},
 8655            cx,
 8656        );
 8657    });
 8658
 8659    editor.update_in(cx, |editor, window, cx| {
 8660        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8661    });
 8662    assert_eq!(
 8663        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 8664        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 8665    );
 8666
 8667    // Trying to expand the selected syntax node one more time has no effect.
 8668    editor.update_in(cx, |editor, window, cx| {
 8669        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8670    });
 8671    assert_eq!(
 8672        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 8673        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 8674    );
 8675
 8676    editor.update_in(cx, |editor, window, cx| {
 8677        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8678    });
 8679    editor.update(cx, |editor, cx| {
 8680        assert_text_with_selections(
 8681            editor,
 8682            indoc! {r#"
 8683                use mod1::mod2::«{mod3, mod4}ˇ»;
 8684
 8685                «ˇfn fn_1(param1: bool, param2: &str) {
 8686                    let var1 = "text";
 8687 8688            "#},
 8689            cx,
 8690        );
 8691    });
 8692
 8693    editor.update_in(cx, |editor, window, cx| {
 8694        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8695    });
 8696    editor.update(cx, |editor, cx| {
 8697        assert_text_with_selections(
 8698            editor,
 8699            indoc! {r#"
 8700                use mod1::mod2::{mod3, «mod4ˇ»};
 8701
 8702                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8703                    let var1 = "«ˇtext»";
 8704                }
 8705            "#},
 8706            cx,
 8707        );
 8708    });
 8709
 8710    editor.update_in(cx, |editor, window, cx| {
 8711        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8712    });
 8713    editor.update(cx, |editor, cx| {
 8714        assert_text_with_selections(
 8715            editor,
 8716            indoc! {r#"
 8717                use mod1::mod2::{mod3, moˇd4};
 8718
 8719                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 8720                    let var1 = "teˇxt";
 8721                }
 8722            "#},
 8723            cx,
 8724        );
 8725    });
 8726
 8727    // Trying to shrink the selected syntax node one more time has no effect.
 8728    editor.update_in(cx, |editor, window, cx| {
 8729        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8730    });
 8731    editor.update_in(cx, |editor, _, cx| {
 8732        assert_text_with_selections(
 8733            editor,
 8734            indoc! {r#"
 8735                use mod1::mod2::{mod3, moˇd4};
 8736
 8737                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 8738                    let var1 = "teˇxt";
 8739                }
 8740            "#},
 8741            cx,
 8742        );
 8743    });
 8744
 8745    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 8746    // a fold.
 8747    editor.update_in(cx, |editor, window, cx| {
 8748        editor.fold_creases(
 8749            vec![
 8750                Crease::simple(
 8751                    Point::new(0, 21)..Point::new(0, 24),
 8752                    FoldPlaceholder::test(),
 8753                ),
 8754                Crease::simple(
 8755                    Point::new(3, 20)..Point::new(3, 22),
 8756                    FoldPlaceholder::test(),
 8757                ),
 8758            ],
 8759            true,
 8760            window,
 8761            cx,
 8762        );
 8763        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8764    });
 8765    editor.update(cx, |editor, cx| {
 8766        assert_text_with_selections(
 8767            editor,
 8768            indoc! {r#"
 8769                use mod1::mod2::«{mod3, mod4}ˇ»;
 8770
 8771                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8772                    let var1 = "«ˇtext»";
 8773                }
 8774            "#},
 8775            cx,
 8776        );
 8777    });
 8778}
 8779
 8780#[gpui::test]
 8781async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 8782    init_test(cx, |_| {});
 8783
 8784    let language = Arc::new(Language::new(
 8785        LanguageConfig::default(),
 8786        Some(tree_sitter_rust::LANGUAGE.into()),
 8787    ));
 8788
 8789    let text = "let a = 2;";
 8790
 8791    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8792    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8793    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8794
 8795    editor
 8796        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8797        .await;
 8798
 8799    // Test case 1: Cursor at end of word
 8800    editor.update_in(cx, |editor, window, cx| {
 8801        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8802            s.select_display_ranges([
 8803                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 8804            ]);
 8805        });
 8806    });
 8807    editor.update(cx, |editor, cx| {
 8808        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 8809    });
 8810    editor.update_in(cx, |editor, window, cx| {
 8811        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8812    });
 8813    editor.update(cx, |editor, cx| {
 8814        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 8815    });
 8816    editor.update_in(cx, |editor, window, cx| {
 8817        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8818    });
 8819    editor.update(cx, |editor, cx| {
 8820        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 8821    });
 8822
 8823    // Test case 2: Cursor at end of statement
 8824    editor.update_in(cx, |editor, window, cx| {
 8825        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8826            s.select_display_ranges([
 8827                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 8828            ]);
 8829        });
 8830    });
 8831    editor.update(cx, |editor, cx| {
 8832        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 8833    });
 8834    editor.update_in(cx, |editor, window, cx| {
 8835        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8836    });
 8837    editor.update(cx, |editor, cx| {
 8838        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 8839    });
 8840}
 8841
 8842#[gpui::test]
 8843async fn test_select_larger_syntax_node_for_cursor_at_symbol(cx: &mut TestAppContext) {
 8844    init_test(cx, |_| {});
 8845
 8846    let language = Arc::new(Language::new(
 8847        LanguageConfig {
 8848            name: "JavaScript".into(),
 8849            ..Default::default()
 8850        },
 8851        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 8852    ));
 8853
 8854    let text = r#"
 8855        let a = {
 8856            key: "value",
 8857        };
 8858    "#
 8859    .unindent();
 8860
 8861    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8862    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8863    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8864
 8865    editor
 8866        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8867        .await;
 8868
 8869    // Test case 1: Cursor after '{'
 8870    editor.update_in(cx, |editor, window, cx| {
 8871        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8872            s.select_display_ranges([
 8873                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 9)
 8874            ]);
 8875        });
 8876    });
 8877    editor.update(cx, |editor, cx| {
 8878        assert_text_with_selections(
 8879            editor,
 8880            indoc! {r#"
 8881                let a = {ˇ
 8882                    key: "value",
 8883                };
 8884            "#},
 8885            cx,
 8886        );
 8887    });
 8888    editor.update_in(cx, |editor, window, cx| {
 8889        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8890    });
 8891    editor.update(cx, |editor, cx| {
 8892        assert_text_with_selections(
 8893            editor,
 8894            indoc! {r#"
 8895                let a = «ˇ{
 8896                    key: "value",
 8897                }»;
 8898            "#},
 8899            cx,
 8900        );
 8901    });
 8902
 8903    // Test case 2: Cursor after ':'
 8904    editor.update_in(cx, |editor, window, cx| {
 8905        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8906            s.select_display_ranges([
 8907                DisplayPoint::new(DisplayRow(1), 8)..DisplayPoint::new(DisplayRow(1), 8)
 8908            ]);
 8909        });
 8910    });
 8911    editor.update(cx, |editor, cx| {
 8912        assert_text_with_selections(
 8913            editor,
 8914            indoc! {r#"
 8915                let a = {
 8916                    key:ˇ "value",
 8917                };
 8918            "#},
 8919            cx,
 8920        );
 8921    });
 8922    editor.update_in(cx, |editor, window, cx| {
 8923        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8924    });
 8925    editor.update(cx, |editor, cx| {
 8926        assert_text_with_selections(
 8927            editor,
 8928            indoc! {r#"
 8929                let a = {
 8930                    «ˇkey: "value"»,
 8931                };
 8932            "#},
 8933            cx,
 8934        );
 8935    });
 8936    editor.update_in(cx, |editor, window, cx| {
 8937        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8938    });
 8939    editor.update(cx, |editor, cx| {
 8940        assert_text_with_selections(
 8941            editor,
 8942            indoc! {r#"
 8943                let a = «ˇ{
 8944                    key: "value",
 8945                }»;
 8946            "#},
 8947            cx,
 8948        );
 8949    });
 8950
 8951    // Test case 3: Cursor after ','
 8952    editor.update_in(cx, |editor, window, cx| {
 8953        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8954            s.select_display_ranges([
 8955                DisplayPoint::new(DisplayRow(1), 17)..DisplayPoint::new(DisplayRow(1), 17)
 8956            ]);
 8957        });
 8958    });
 8959    editor.update(cx, |editor, cx| {
 8960        assert_text_with_selections(
 8961            editor,
 8962            indoc! {r#"
 8963                let a = {
 8964                    key: "value",ˇ
 8965                };
 8966            "#},
 8967            cx,
 8968        );
 8969    });
 8970    editor.update_in(cx, |editor, window, cx| {
 8971        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8972    });
 8973    editor.update(cx, |editor, cx| {
 8974        assert_text_with_selections(
 8975            editor,
 8976            indoc! {r#"
 8977                let a = «ˇ{
 8978                    key: "value",
 8979                }»;
 8980            "#},
 8981            cx,
 8982        );
 8983    });
 8984
 8985    // Test case 4: Cursor after ';'
 8986    editor.update_in(cx, |editor, window, cx| {
 8987        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8988            s.select_display_ranges([
 8989                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)
 8990            ]);
 8991        });
 8992    });
 8993    editor.update(cx, |editor, cx| {
 8994        assert_text_with_selections(
 8995            editor,
 8996            indoc! {r#"
 8997                let a = {
 8998                    key: "value",
 8999                };ˇ
 9000            "#},
 9001            cx,
 9002        );
 9003    });
 9004    editor.update_in(cx, |editor, window, cx| {
 9005        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9006    });
 9007    editor.update(cx, |editor, cx| {
 9008        assert_text_with_selections(
 9009            editor,
 9010            indoc! {r#"
 9011                «ˇlet a = {
 9012                    key: "value",
 9013                };
 9014                »"#},
 9015            cx,
 9016        );
 9017    });
 9018}
 9019
 9020#[gpui::test]
 9021async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 9022    init_test(cx, |_| {});
 9023
 9024    let language = Arc::new(Language::new(
 9025        LanguageConfig::default(),
 9026        Some(tree_sitter_rust::LANGUAGE.into()),
 9027    ));
 9028
 9029    let text = r#"
 9030        use mod1::mod2::{mod3, mod4};
 9031
 9032        fn fn_1(param1: bool, param2: &str) {
 9033            let var1 = "hello world";
 9034        }
 9035    "#
 9036    .unindent();
 9037
 9038    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9039    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9040    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9041
 9042    editor
 9043        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9044        .await;
 9045
 9046    // Test 1: Cursor on a letter of a string word
 9047    editor.update_in(cx, |editor, window, cx| {
 9048        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9049            s.select_display_ranges([
 9050                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 9051            ]);
 9052        });
 9053    });
 9054    editor.update_in(cx, |editor, window, cx| {
 9055        assert_text_with_selections(
 9056            editor,
 9057            indoc! {r#"
 9058                use mod1::mod2::{mod3, mod4};
 9059
 9060                fn fn_1(param1: bool, param2: &str) {
 9061                    let var1 = "hˇello world";
 9062                }
 9063            "#},
 9064            cx,
 9065        );
 9066        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9067        assert_text_with_selections(
 9068            editor,
 9069            indoc! {r#"
 9070                use mod1::mod2::{mod3, mod4};
 9071
 9072                fn fn_1(param1: bool, param2: &str) {
 9073                    let var1 = "«ˇhello» world";
 9074                }
 9075            "#},
 9076            cx,
 9077        );
 9078    });
 9079
 9080    // Test 2: Partial selection within a word
 9081    editor.update_in(cx, |editor, window, cx| {
 9082        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9083            s.select_display_ranges([
 9084                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 9085            ]);
 9086        });
 9087    });
 9088    editor.update_in(cx, |editor, window, cx| {
 9089        assert_text_with_selections(
 9090            editor,
 9091            indoc! {r#"
 9092                use mod1::mod2::{mod3, mod4};
 9093
 9094                fn fn_1(param1: bool, param2: &str) {
 9095                    let var1 = "h«elˇ»lo world";
 9096                }
 9097            "#},
 9098            cx,
 9099        );
 9100        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9101        assert_text_with_selections(
 9102            editor,
 9103            indoc! {r#"
 9104                use mod1::mod2::{mod3, mod4};
 9105
 9106                fn fn_1(param1: bool, param2: &str) {
 9107                    let var1 = "«ˇhello» world";
 9108                }
 9109            "#},
 9110            cx,
 9111        );
 9112    });
 9113
 9114    // Test 3: Complete word already selected
 9115    editor.update_in(cx, |editor, window, cx| {
 9116        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9117            s.select_display_ranges([
 9118                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 9119            ]);
 9120        });
 9121    });
 9122    editor.update_in(cx, |editor, window, cx| {
 9123        assert_text_with_selections(
 9124            editor,
 9125            indoc! {r#"
 9126                use mod1::mod2::{mod3, mod4};
 9127
 9128                fn fn_1(param1: bool, param2: &str) {
 9129                    let var1 = "«helloˇ» world";
 9130                }
 9131            "#},
 9132            cx,
 9133        );
 9134        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9135        assert_text_with_selections(
 9136            editor,
 9137            indoc! {r#"
 9138                use mod1::mod2::{mod3, mod4};
 9139
 9140                fn fn_1(param1: bool, param2: &str) {
 9141                    let var1 = "«hello worldˇ»";
 9142                }
 9143            "#},
 9144            cx,
 9145        );
 9146    });
 9147
 9148    // Test 4: Selection spanning across words
 9149    editor.update_in(cx, |editor, window, cx| {
 9150        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9151            s.select_display_ranges([
 9152                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 9153            ]);
 9154        });
 9155    });
 9156    editor.update_in(cx, |editor, window, cx| {
 9157        assert_text_with_selections(
 9158            editor,
 9159            indoc! {r#"
 9160                use mod1::mod2::{mod3, mod4};
 9161
 9162                fn fn_1(param1: bool, param2: &str) {
 9163                    let var1 = "hel«lo woˇ»rld";
 9164                }
 9165            "#},
 9166            cx,
 9167        );
 9168        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9169        assert_text_with_selections(
 9170            editor,
 9171            indoc! {r#"
 9172                use mod1::mod2::{mod3, mod4};
 9173
 9174                fn fn_1(param1: bool, param2: &str) {
 9175                    let var1 = "«ˇhello world»";
 9176                }
 9177            "#},
 9178            cx,
 9179        );
 9180    });
 9181
 9182    // Test 5: Expansion beyond string
 9183    editor.update_in(cx, |editor, window, cx| {
 9184        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9185        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9186        assert_text_with_selections(
 9187            editor,
 9188            indoc! {r#"
 9189                use mod1::mod2::{mod3, mod4};
 9190
 9191                fn fn_1(param1: bool, param2: &str) {
 9192                    «ˇlet var1 = "hello world";»
 9193                }
 9194            "#},
 9195            cx,
 9196        );
 9197    });
 9198}
 9199
 9200#[gpui::test]
 9201async fn test_unwrap_syntax_nodes(cx: &mut gpui::TestAppContext) {
 9202    init_test(cx, |_| {});
 9203
 9204    let mut cx = EditorTestContext::new(cx).await;
 9205
 9206    let language = Arc::new(Language::new(
 9207        LanguageConfig::default(),
 9208        Some(tree_sitter_rust::LANGUAGE.into()),
 9209    ));
 9210
 9211    cx.update_buffer(|buffer, cx| {
 9212        buffer.set_language(Some(language), cx);
 9213    });
 9214
 9215    cx.set_state(indoc! { r#"use mod1::{mod2::{«mod3ˇ», mod4}, mod5::{mod6, «mod7ˇ»}};"# });
 9216    cx.update_editor(|editor, window, cx| {
 9217        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9218    });
 9219
 9220    cx.assert_editor_state(indoc! { r#"use mod1::{mod2::«mod3ˇ», mod5::«mod7ˇ»};"# });
 9221
 9222    cx.set_state(indoc! { r#"fn a() {
 9223          // what
 9224          // a
 9225          // ˇlong
 9226          // method
 9227          // I
 9228          // sure
 9229          // hope
 9230          // it
 9231          // works
 9232    }"# });
 9233
 9234    let buffer = cx.update_multibuffer(|multibuffer, _| multibuffer.as_singleton().unwrap());
 9235    let multi_buffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
 9236    cx.update(|_, cx| {
 9237        multi_buffer.update(cx, |multi_buffer, cx| {
 9238            multi_buffer.set_excerpts_for_path(
 9239                PathKey::for_buffer(&buffer, cx),
 9240                buffer,
 9241                [Point::new(1, 0)..Point::new(1, 0)],
 9242                3,
 9243                cx,
 9244            );
 9245        });
 9246    });
 9247
 9248    let editor2 = cx.new_window_entity(|window, cx| {
 9249        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
 9250    });
 9251
 9252    let mut cx = EditorTestContext::for_editor_in(editor2, &mut cx).await;
 9253    cx.update_editor(|editor, window, cx| {
 9254        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 9255            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)]);
 9256        })
 9257    });
 9258
 9259    cx.assert_editor_state(indoc! { "
 9260        fn a() {
 9261              // what
 9262              // a
 9263        ˇ      // long
 9264              // method"});
 9265
 9266    cx.update_editor(|editor, window, cx| {
 9267        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9268    });
 9269
 9270    // Although we could potentially make the action work when the syntax node
 9271    // is half-hidden, it seems a bit dangerous as you can't easily tell what it
 9272    // did. Maybe we could also expand the excerpt to contain the range?
 9273    cx.assert_editor_state(indoc! { "
 9274        fn a() {
 9275              // what
 9276              // a
 9277        ˇ      // long
 9278              // method"});
 9279}
 9280
 9281#[gpui::test]
 9282async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 9283    init_test(cx, |_| {});
 9284
 9285    let base_text = r#"
 9286        impl A {
 9287            // this is an uncommitted comment
 9288
 9289            fn b() {
 9290                c();
 9291            }
 9292
 9293            // this is another uncommitted comment
 9294
 9295            fn d() {
 9296                // e
 9297                // f
 9298            }
 9299        }
 9300
 9301        fn g() {
 9302            // h
 9303        }
 9304    "#
 9305    .unindent();
 9306
 9307    let text = r#"
 9308        ˇimpl A {
 9309
 9310            fn b() {
 9311                c();
 9312            }
 9313
 9314            fn d() {
 9315                // e
 9316                // f
 9317            }
 9318        }
 9319
 9320        fn g() {
 9321            // h
 9322        }
 9323    "#
 9324    .unindent();
 9325
 9326    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9327    cx.set_state(&text);
 9328    cx.set_head_text(&base_text);
 9329    cx.update_editor(|editor, window, cx| {
 9330        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 9331    });
 9332
 9333    cx.assert_state_with_diff(
 9334        "
 9335        ˇimpl A {
 9336      -     // this is an uncommitted comment
 9337
 9338            fn b() {
 9339                c();
 9340            }
 9341
 9342      -     // this is another uncommitted comment
 9343      -
 9344            fn d() {
 9345                // e
 9346                // f
 9347            }
 9348        }
 9349
 9350        fn g() {
 9351            // h
 9352        }
 9353    "
 9354        .unindent(),
 9355    );
 9356
 9357    let expected_display_text = "
 9358        impl A {
 9359            // this is an uncommitted comment
 9360
 9361            fn b() {
 9362 9363            }
 9364
 9365            // this is another uncommitted comment
 9366
 9367            fn d() {
 9368 9369            }
 9370        }
 9371
 9372        fn g() {
 9373 9374        }
 9375        "
 9376    .unindent();
 9377
 9378    cx.update_editor(|editor, window, cx| {
 9379        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 9380        assert_eq!(editor.display_text(cx), expected_display_text);
 9381    });
 9382}
 9383
 9384#[gpui::test]
 9385async fn test_autoindent(cx: &mut TestAppContext) {
 9386    init_test(cx, |_| {});
 9387
 9388    let language = Arc::new(
 9389        Language::new(
 9390            LanguageConfig {
 9391                brackets: BracketPairConfig {
 9392                    pairs: vec![
 9393                        BracketPair {
 9394                            start: "{".to_string(),
 9395                            end: "}".to_string(),
 9396                            close: false,
 9397                            surround: false,
 9398                            newline: true,
 9399                        },
 9400                        BracketPair {
 9401                            start: "(".to_string(),
 9402                            end: ")".to_string(),
 9403                            close: false,
 9404                            surround: false,
 9405                            newline: true,
 9406                        },
 9407                    ],
 9408                    ..Default::default()
 9409                },
 9410                ..Default::default()
 9411            },
 9412            Some(tree_sitter_rust::LANGUAGE.into()),
 9413        )
 9414        .with_indents_query(
 9415            r#"
 9416                (_ "(" ")" @end) @indent
 9417                (_ "{" "}" @end) @indent
 9418            "#,
 9419        )
 9420        .unwrap(),
 9421    );
 9422
 9423    let text = "fn a() {}";
 9424
 9425    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9426    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9427    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9428    editor
 9429        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9430        .await;
 9431
 9432    editor.update_in(cx, |editor, window, cx| {
 9433        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9434            s.select_ranges([5..5, 8..8, 9..9])
 9435        });
 9436        editor.newline(&Newline, window, cx);
 9437        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 9438        assert_eq!(
 9439            editor.selections.ranges(cx),
 9440            &[
 9441                Point::new(1, 4)..Point::new(1, 4),
 9442                Point::new(3, 4)..Point::new(3, 4),
 9443                Point::new(5, 0)..Point::new(5, 0)
 9444            ]
 9445        );
 9446    });
 9447}
 9448
 9449#[gpui::test]
 9450async fn test_autoindent_disabled(cx: &mut TestAppContext) {
 9451    init_test(cx, |settings| settings.defaults.auto_indent = Some(false));
 9452
 9453    let language = Arc::new(
 9454        Language::new(
 9455            LanguageConfig {
 9456                brackets: BracketPairConfig {
 9457                    pairs: vec![
 9458                        BracketPair {
 9459                            start: "{".to_string(),
 9460                            end: "}".to_string(),
 9461                            close: false,
 9462                            surround: false,
 9463                            newline: true,
 9464                        },
 9465                        BracketPair {
 9466                            start: "(".to_string(),
 9467                            end: ")".to_string(),
 9468                            close: false,
 9469                            surround: false,
 9470                            newline: true,
 9471                        },
 9472                    ],
 9473                    ..Default::default()
 9474                },
 9475                ..Default::default()
 9476            },
 9477            Some(tree_sitter_rust::LANGUAGE.into()),
 9478        )
 9479        .with_indents_query(
 9480            r#"
 9481                (_ "(" ")" @end) @indent
 9482                (_ "{" "}" @end) @indent
 9483            "#,
 9484        )
 9485        .unwrap(),
 9486    );
 9487
 9488    let text = "fn a() {}";
 9489
 9490    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9491    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9492    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9493    editor
 9494        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9495        .await;
 9496
 9497    editor.update_in(cx, |editor, window, cx| {
 9498        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9499            s.select_ranges([5..5, 8..8, 9..9])
 9500        });
 9501        editor.newline(&Newline, window, cx);
 9502        assert_eq!(
 9503            editor.text(cx),
 9504            indoc!(
 9505                "
 9506                fn a(
 9507
 9508                ) {
 9509
 9510                }
 9511                "
 9512            )
 9513        );
 9514        assert_eq!(
 9515            editor.selections.ranges(cx),
 9516            &[
 9517                Point::new(1, 0)..Point::new(1, 0),
 9518                Point::new(3, 0)..Point::new(3, 0),
 9519                Point::new(5, 0)..Point::new(5, 0)
 9520            ]
 9521        );
 9522    });
 9523}
 9524
 9525#[gpui::test]
 9526async fn test_autoindent_disabled_with_nested_language(cx: &mut TestAppContext) {
 9527    init_test(cx, |settings| {
 9528        settings.defaults.auto_indent = Some(true);
 9529        settings.languages.0.insert(
 9530            "python".into(),
 9531            LanguageSettingsContent {
 9532                auto_indent: Some(false),
 9533                ..Default::default()
 9534            },
 9535        );
 9536    });
 9537
 9538    let mut cx = EditorTestContext::new(cx).await;
 9539
 9540    let injected_language = Arc::new(
 9541        Language::new(
 9542            LanguageConfig {
 9543                brackets: BracketPairConfig {
 9544                    pairs: vec![
 9545                        BracketPair {
 9546                            start: "{".to_string(),
 9547                            end: "}".to_string(),
 9548                            close: false,
 9549                            surround: false,
 9550                            newline: true,
 9551                        },
 9552                        BracketPair {
 9553                            start: "(".to_string(),
 9554                            end: ")".to_string(),
 9555                            close: true,
 9556                            surround: false,
 9557                            newline: true,
 9558                        },
 9559                    ],
 9560                    ..Default::default()
 9561                },
 9562                name: "python".into(),
 9563                ..Default::default()
 9564            },
 9565            Some(tree_sitter_python::LANGUAGE.into()),
 9566        )
 9567        .with_indents_query(
 9568            r#"
 9569                (_ "(" ")" @end) @indent
 9570                (_ "{" "}" @end) @indent
 9571            "#,
 9572        )
 9573        .unwrap(),
 9574    );
 9575
 9576    let language = Arc::new(
 9577        Language::new(
 9578            LanguageConfig {
 9579                brackets: BracketPairConfig {
 9580                    pairs: vec![
 9581                        BracketPair {
 9582                            start: "{".to_string(),
 9583                            end: "}".to_string(),
 9584                            close: false,
 9585                            surround: false,
 9586                            newline: true,
 9587                        },
 9588                        BracketPair {
 9589                            start: "(".to_string(),
 9590                            end: ")".to_string(),
 9591                            close: true,
 9592                            surround: false,
 9593                            newline: true,
 9594                        },
 9595                    ],
 9596                    ..Default::default()
 9597                },
 9598                name: LanguageName::new("rust"),
 9599                ..Default::default()
 9600            },
 9601            Some(tree_sitter_rust::LANGUAGE.into()),
 9602        )
 9603        .with_indents_query(
 9604            r#"
 9605                (_ "(" ")" @end) @indent
 9606                (_ "{" "}" @end) @indent
 9607            "#,
 9608        )
 9609        .unwrap()
 9610        .with_injection_query(
 9611            r#"
 9612            (macro_invocation
 9613                macro: (identifier) @_macro_name
 9614                (token_tree) @injection.content
 9615                (#set! injection.language "python"))
 9616           "#,
 9617        )
 9618        .unwrap(),
 9619    );
 9620
 9621    cx.language_registry().add(injected_language);
 9622    cx.language_registry().add(language.clone());
 9623
 9624    cx.update_buffer(|buffer, cx| {
 9625        buffer.set_language(Some(language), cx);
 9626    });
 9627
 9628    cx.set_state(r#"struct A {ˇ}"#);
 9629
 9630    cx.update_editor(|editor, window, cx| {
 9631        editor.newline(&Default::default(), window, cx);
 9632    });
 9633
 9634    cx.assert_editor_state(indoc!(
 9635        "struct A {
 9636            ˇ
 9637        }"
 9638    ));
 9639
 9640    cx.set_state(r#"select_biased!(ˇ)"#);
 9641
 9642    cx.update_editor(|editor, window, cx| {
 9643        editor.newline(&Default::default(), window, cx);
 9644        editor.handle_input("def ", window, cx);
 9645        editor.handle_input("(", window, cx);
 9646        editor.newline(&Default::default(), window, cx);
 9647        editor.handle_input("a", window, cx);
 9648    });
 9649
 9650    cx.assert_editor_state(indoc!(
 9651        "select_biased!(
 9652        def (
 9653 9654        )
 9655        )"
 9656    ));
 9657}
 9658
 9659#[gpui::test]
 9660async fn test_autoindent_selections(cx: &mut TestAppContext) {
 9661    init_test(cx, |_| {});
 9662
 9663    {
 9664        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9665        cx.set_state(indoc! {"
 9666            impl A {
 9667
 9668                fn b() {}
 9669
 9670            «fn c() {
 9671
 9672            }ˇ»
 9673            }
 9674        "});
 9675
 9676        cx.update_editor(|editor, window, cx| {
 9677            editor.autoindent(&Default::default(), window, cx);
 9678        });
 9679
 9680        cx.assert_editor_state(indoc! {"
 9681            impl A {
 9682
 9683                fn b() {}
 9684
 9685                «fn c() {
 9686
 9687                }ˇ»
 9688            }
 9689        "});
 9690    }
 9691
 9692    {
 9693        let mut cx = EditorTestContext::new_multibuffer(
 9694            cx,
 9695            [indoc! { "
 9696                impl A {
 9697                «
 9698                // a
 9699                fn b(){}
 9700                »
 9701                «
 9702                    }
 9703                    fn c(){}
 9704                »
 9705            "}],
 9706        );
 9707
 9708        let buffer = cx.update_editor(|editor, _, cx| {
 9709            let buffer = editor.buffer().update(cx, |buffer, _| {
 9710                buffer.all_buffers().iter().next().unwrap().clone()
 9711            });
 9712            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 9713            buffer
 9714        });
 9715
 9716        cx.run_until_parked();
 9717        cx.update_editor(|editor, window, cx| {
 9718            editor.select_all(&Default::default(), window, cx);
 9719            editor.autoindent(&Default::default(), window, cx)
 9720        });
 9721        cx.run_until_parked();
 9722
 9723        cx.update(|_, cx| {
 9724            assert_eq!(
 9725                buffer.read(cx).text(),
 9726                indoc! { "
 9727                    impl A {
 9728
 9729                        // a
 9730                        fn b(){}
 9731
 9732
 9733                    }
 9734                    fn c(){}
 9735
 9736                " }
 9737            )
 9738        });
 9739    }
 9740}
 9741
 9742#[gpui::test]
 9743async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 9744    init_test(cx, |_| {});
 9745
 9746    let mut cx = EditorTestContext::new(cx).await;
 9747
 9748    let language = Arc::new(Language::new(
 9749        LanguageConfig {
 9750            brackets: BracketPairConfig {
 9751                pairs: vec![
 9752                    BracketPair {
 9753                        start: "{".to_string(),
 9754                        end: "}".to_string(),
 9755                        close: true,
 9756                        surround: true,
 9757                        newline: true,
 9758                    },
 9759                    BracketPair {
 9760                        start: "(".to_string(),
 9761                        end: ")".to_string(),
 9762                        close: true,
 9763                        surround: true,
 9764                        newline: true,
 9765                    },
 9766                    BracketPair {
 9767                        start: "/*".to_string(),
 9768                        end: " */".to_string(),
 9769                        close: true,
 9770                        surround: true,
 9771                        newline: true,
 9772                    },
 9773                    BracketPair {
 9774                        start: "[".to_string(),
 9775                        end: "]".to_string(),
 9776                        close: false,
 9777                        surround: false,
 9778                        newline: true,
 9779                    },
 9780                    BracketPair {
 9781                        start: "\"".to_string(),
 9782                        end: "\"".to_string(),
 9783                        close: true,
 9784                        surround: true,
 9785                        newline: false,
 9786                    },
 9787                    BracketPair {
 9788                        start: "<".to_string(),
 9789                        end: ">".to_string(),
 9790                        close: false,
 9791                        surround: true,
 9792                        newline: true,
 9793                    },
 9794                ],
 9795                ..Default::default()
 9796            },
 9797            autoclose_before: "})]".to_string(),
 9798            ..Default::default()
 9799        },
 9800        Some(tree_sitter_rust::LANGUAGE.into()),
 9801    ));
 9802
 9803    cx.language_registry().add(language.clone());
 9804    cx.update_buffer(|buffer, cx| {
 9805        buffer.set_language(Some(language), cx);
 9806    });
 9807
 9808    cx.set_state(
 9809        &r#"
 9810            🏀ˇ
 9811            εˇ
 9812            ❤️ˇ
 9813        "#
 9814        .unindent(),
 9815    );
 9816
 9817    // autoclose multiple nested brackets at multiple cursors
 9818    cx.update_editor(|editor, window, cx| {
 9819        editor.handle_input("{", window, cx);
 9820        editor.handle_input("{", window, cx);
 9821        editor.handle_input("{", window, cx);
 9822    });
 9823    cx.assert_editor_state(
 9824        &"
 9825            🏀{{{ˇ}}}
 9826            ε{{{ˇ}}}
 9827            ❤️{{{ˇ}}}
 9828        "
 9829        .unindent(),
 9830    );
 9831
 9832    // insert a different closing bracket
 9833    cx.update_editor(|editor, window, cx| {
 9834        editor.handle_input(")", window, cx);
 9835    });
 9836    cx.assert_editor_state(
 9837        &"
 9838            🏀{{{)ˇ}}}
 9839            ε{{{)ˇ}}}
 9840            ❤️{{{)ˇ}}}
 9841        "
 9842        .unindent(),
 9843    );
 9844
 9845    // skip over the auto-closed brackets when typing a closing bracket
 9846    cx.update_editor(|editor, window, cx| {
 9847        editor.move_right(&MoveRight, window, cx);
 9848        editor.handle_input("}", window, cx);
 9849        editor.handle_input("}", window, cx);
 9850        editor.handle_input("}", window, cx);
 9851    });
 9852    cx.assert_editor_state(
 9853        &"
 9854            🏀{{{)}}}}ˇ
 9855            ε{{{)}}}}ˇ
 9856            ❤️{{{)}}}}ˇ
 9857        "
 9858        .unindent(),
 9859    );
 9860
 9861    // autoclose multi-character pairs
 9862    cx.set_state(
 9863        &"
 9864            ˇ
 9865            ˇ
 9866        "
 9867        .unindent(),
 9868    );
 9869    cx.update_editor(|editor, window, cx| {
 9870        editor.handle_input("/", window, cx);
 9871        editor.handle_input("*", window, cx);
 9872    });
 9873    cx.assert_editor_state(
 9874        &"
 9875            /*ˇ */
 9876            /*ˇ */
 9877        "
 9878        .unindent(),
 9879    );
 9880
 9881    // one cursor autocloses a multi-character pair, one cursor
 9882    // does not autoclose.
 9883    cx.set_state(
 9884        &"
 9885 9886            ˇ
 9887        "
 9888        .unindent(),
 9889    );
 9890    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
 9891    cx.assert_editor_state(
 9892        &"
 9893            /*ˇ */
 9894 9895        "
 9896        .unindent(),
 9897    );
 9898
 9899    // Don't autoclose if the next character isn't whitespace and isn't
 9900    // listed in the language's "autoclose_before" section.
 9901    cx.set_state("ˇa b");
 9902    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 9903    cx.assert_editor_state("{ˇa b");
 9904
 9905    // Don't autoclose if `close` is false for the bracket pair
 9906    cx.set_state("ˇ");
 9907    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
 9908    cx.assert_editor_state("");
 9909
 9910    // Surround with brackets if text is selected
 9911    cx.set_state("«aˇ» b");
 9912    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 9913    cx.assert_editor_state("{«aˇ»} b");
 9914
 9915    // Autoclose when not immediately after a word character
 9916    cx.set_state("a ˇ");
 9917    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9918    cx.assert_editor_state("a \"ˇ\"");
 9919
 9920    // Autoclose pair where the start and end characters are the same
 9921    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9922    cx.assert_editor_state("a \"\"ˇ");
 9923
 9924    // Don't autoclose when immediately after a word character
 9925    cx.set_state("");
 9926    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9927    cx.assert_editor_state("a\"ˇ");
 9928
 9929    // Do autoclose when after a non-word character
 9930    cx.set_state("");
 9931    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9932    cx.assert_editor_state("{\"ˇ\"");
 9933
 9934    // Non identical pairs autoclose regardless of preceding character
 9935    cx.set_state("");
 9936    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 9937    cx.assert_editor_state("a{ˇ}");
 9938
 9939    // Don't autoclose pair if autoclose is disabled
 9940    cx.set_state("ˇ");
 9941    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 9942    cx.assert_editor_state("");
 9943
 9944    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
 9945    cx.set_state("«aˇ» b");
 9946    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 9947    cx.assert_editor_state("<«aˇ»> b");
 9948}
 9949
 9950#[gpui::test]
 9951async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
 9952    init_test(cx, |settings| {
 9953        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 9954    });
 9955
 9956    let mut cx = EditorTestContext::new(cx).await;
 9957
 9958    let language = Arc::new(Language::new(
 9959        LanguageConfig {
 9960            brackets: BracketPairConfig {
 9961                pairs: vec![
 9962                    BracketPair {
 9963                        start: "{".to_string(),
 9964                        end: "}".to_string(),
 9965                        close: true,
 9966                        surround: true,
 9967                        newline: true,
 9968                    },
 9969                    BracketPair {
 9970                        start: "(".to_string(),
 9971                        end: ")".to_string(),
 9972                        close: true,
 9973                        surround: true,
 9974                        newline: true,
 9975                    },
 9976                    BracketPair {
 9977                        start: "[".to_string(),
 9978                        end: "]".to_string(),
 9979                        close: false,
 9980                        surround: false,
 9981                        newline: true,
 9982                    },
 9983                ],
 9984                ..Default::default()
 9985            },
 9986            autoclose_before: "})]".to_string(),
 9987            ..Default::default()
 9988        },
 9989        Some(tree_sitter_rust::LANGUAGE.into()),
 9990    ));
 9991
 9992    cx.language_registry().add(language.clone());
 9993    cx.update_buffer(|buffer, cx| {
 9994        buffer.set_language(Some(language), cx);
 9995    });
 9996
 9997    cx.set_state(
 9998        &"
 9999            ˇ
10000            ˇ
10001            ˇ
10002        "
10003        .unindent(),
10004    );
10005
10006    // ensure only matching closing brackets are skipped over
10007    cx.update_editor(|editor, window, cx| {
10008        editor.handle_input("}", window, cx);
10009        editor.move_left(&MoveLeft, window, cx);
10010        editor.handle_input(")", window, cx);
10011        editor.move_left(&MoveLeft, window, cx);
10012    });
10013    cx.assert_editor_state(
10014        &"
10015            ˇ)}
10016            ˇ)}
10017            ˇ)}
10018        "
10019        .unindent(),
10020    );
10021
10022    // skip-over closing brackets at multiple cursors
10023    cx.update_editor(|editor, window, cx| {
10024        editor.handle_input(")", window, cx);
10025        editor.handle_input("}", window, cx);
10026    });
10027    cx.assert_editor_state(
10028        &"
10029            )}ˇ
10030            )}ˇ
10031            )}ˇ
10032        "
10033        .unindent(),
10034    );
10035
10036    // ignore non-close brackets
10037    cx.update_editor(|editor, window, cx| {
10038        editor.handle_input("]", window, cx);
10039        editor.move_left(&MoveLeft, window, cx);
10040        editor.handle_input("]", window, cx);
10041    });
10042    cx.assert_editor_state(
10043        &"
10044            )}]ˇ]
10045            )}]ˇ]
10046            )}]ˇ]
10047        "
10048        .unindent(),
10049    );
10050}
10051
10052#[gpui::test]
10053async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
10054    init_test(cx, |_| {});
10055
10056    let mut cx = EditorTestContext::new(cx).await;
10057
10058    let html_language = Arc::new(
10059        Language::new(
10060            LanguageConfig {
10061                name: "HTML".into(),
10062                brackets: BracketPairConfig {
10063                    pairs: vec![
10064                        BracketPair {
10065                            start: "<".into(),
10066                            end: ">".into(),
10067                            close: true,
10068                            ..Default::default()
10069                        },
10070                        BracketPair {
10071                            start: "{".into(),
10072                            end: "}".into(),
10073                            close: true,
10074                            ..Default::default()
10075                        },
10076                        BracketPair {
10077                            start: "(".into(),
10078                            end: ")".into(),
10079                            close: true,
10080                            ..Default::default()
10081                        },
10082                    ],
10083                    ..Default::default()
10084                },
10085                autoclose_before: "})]>".into(),
10086                ..Default::default()
10087            },
10088            Some(tree_sitter_html::LANGUAGE.into()),
10089        )
10090        .with_injection_query(
10091            r#"
10092            (script_element
10093                (raw_text) @injection.content
10094                (#set! injection.language "javascript"))
10095            "#,
10096        )
10097        .unwrap(),
10098    );
10099
10100    let javascript_language = Arc::new(Language::new(
10101        LanguageConfig {
10102            name: "JavaScript".into(),
10103            brackets: BracketPairConfig {
10104                pairs: vec![
10105                    BracketPair {
10106                        start: "/*".into(),
10107                        end: " */".into(),
10108                        close: true,
10109                        ..Default::default()
10110                    },
10111                    BracketPair {
10112                        start: "{".into(),
10113                        end: "}".into(),
10114                        close: true,
10115                        ..Default::default()
10116                    },
10117                    BracketPair {
10118                        start: "(".into(),
10119                        end: ")".into(),
10120                        close: true,
10121                        ..Default::default()
10122                    },
10123                ],
10124                ..Default::default()
10125            },
10126            autoclose_before: "})]>".into(),
10127            ..Default::default()
10128        },
10129        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10130    ));
10131
10132    cx.language_registry().add(html_language.clone());
10133    cx.language_registry().add(javascript_language);
10134    cx.executor().run_until_parked();
10135
10136    cx.update_buffer(|buffer, cx| {
10137        buffer.set_language(Some(html_language), cx);
10138    });
10139
10140    cx.set_state(
10141        &r#"
10142            <body>ˇ
10143                <script>
10144                    var x = 1;ˇ
10145                </script>
10146            </body>ˇ
10147        "#
10148        .unindent(),
10149    );
10150
10151    // Precondition: different languages are active at different locations.
10152    cx.update_editor(|editor, window, cx| {
10153        let snapshot = editor.snapshot(window, cx);
10154        let cursors = editor.selections.ranges::<usize>(cx);
10155        let languages = cursors
10156            .iter()
10157            .map(|c| snapshot.language_at(c.start).unwrap().name())
10158            .collect::<Vec<_>>();
10159        assert_eq!(
10160            languages,
10161            &["HTML".into(), "JavaScript".into(), "HTML".into()]
10162        );
10163    });
10164
10165    // Angle brackets autoclose in HTML, but not JavaScript.
10166    cx.update_editor(|editor, window, cx| {
10167        editor.handle_input("<", window, cx);
10168        editor.handle_input("a", window, cx);
10169    });
10170    cx.assert_editor_state(
10171        &r#"
10172            <body><aˇ>
10173                <script>
10174                    var x = 1;<aˇ
10175                </script>
10176            </body><aˇ>
10177        "#
10178        .unindent(),
10179    );
10180
10181    // Curly braces and parens autoclose in both HTML and JavaScript.
10182    cx.update_editor(|editor, window, cx| {
10183        editor.handle_input(" b=", window, cx);
10184        editor.handle_input("{", window, cx);
10185        editor.handle_input("c", window, cx);
10186        editor.handle_input("(", window, cx);
10187    });
10188    cx.assert_editor_state(
10189        &r#"
10190            <body><a b={c(ˇ)}>
10191                <script>
10192                    var x = 1;<a b={c(ˇ)}
10193                </script>
10194            </body><a b={c(ˇ)}>
10195        "#
10196        .unindent(),
10197    );
10198
10199    // Brackets that were already autoclosed are skipped.
10200    cx.update_editor(|editor, window, cx| {
10201        editor.handle_input(")", window, cx);
10202        editor.handle_input("d", window, cx);
10203        editor.handle_input("}", window, cx);
10204    });
10205    cx.assert_editor_state(
10206        &r#"
10207            <body><a b={c()d}ˇ>
10208                <script>
10209                    var x = 1;<a b={c()d}ˇ
10210                </script>
10211            </body><a b={c()d}ˇ>
10212        "#
10213        .unindent(),
10214    );
10215    cx.update_editor(|editor, window, cx| {
10216        editor.handle_input(">", window, cx);
10217    });
10218    cx.assert_editor_state(
10219        &r#"
10220            <body><a b={c()d}>ˇ
10221                <script>
10222                    var x = 1;<a b={c()d}>ˇ
10223                </script>
10224            </body><a b={c()d}>ˇ
10225        "#
10226        .unindent(),
10227    );
10228
10229    // Reset
10230    cx.set_state(
10231        &r#"
10232            <body>ˇ
10233                <script>
10234                    var x = 1;ˇ
10235                </script>
10236            </body>ˇ
10237        "#
10238        .unindent(),
10239    );
10240
10241    cx.update_editor(|editor, window, cx| {
10242        editor.handle_input("<", window, cx);
10243    });
10244    cx.assert_editor_state(
10245        &r#"
10246            <body><ˇ>
10247                <script>
10248                    var x = 1;<ˇ
10249                </script>
10250            </body><ˇ>
10251        "#
10252        .unindent(),
10253    );
10254
10255    // When backspacing, the closing angle brackets are removed.
10256    cx.update_editor(|editor, window, cx| {
10257        editor.backspace(&Backspace, window, cx);
10258    });
10259    cx.assert_editor_state(
10260        &r#"
10261            <body>ˇ
10262                <script>
10263                    var x = 1;ˇ
10264                </script>
10265            </body>ˇ
10266        "#
10267        .unindent(),
10268    );
10269
10270    // Block comments autoclose in JavaScript, but not HTML.
10271    cx.update_editor(|editor, window, cx| {
10272        editor.handle_input("/", window, cx);
10273        editor.handle_input("*", window, cx);
10274    });
10275    cx.assert_editor_state(
10276        &r#"
10277            <body>/*ˇ
10278                <script>
10279                    var x = 1;/*ˇ */
10280                </script>
10281            </body>/*ˇ
10282        "#
10283        .unindent(),
10284    );
10285}
10286
10287#[gpui::test]
10288async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
10289    init_test(cx, |_| {});
10290
10291    let mut cx = EditorTestContext::new(cx).await;
10292
10293    let rust_language = Arc::new(
10294        Language::new(
10295            LanguageConfig {
10296                name: "Rust".into(),
10297                brackets: serde_json::from_value(json!([
10298                    { "start": "{", "end": "}", "close": true, "newline": true },
10299                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
10300                ]))
10301                .unwrap(),
10302                autoclose_before: "})]>".into(),
10303                ..Default::default()
10304            },
10305            Some(tree_sitter_rust::LANGUAGE.into()),
10306        )
10307        .with_override_query("(string_literal) @string")
10308        .unwrap(),
10309    );
10310
10311    cx.language_registry().add(rust_language.clone());
10312    cx.update_buffer(|buffer, cx| {
10313        buffer.set_language(Some(rust_language), cx);
10314    });
10315
10316    cx.set_state(
10317        &r#"
10318            let x = ˇ
10319        "#
10320        .unindent(),
10321    );
10322
10323    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
10324    cx.update_editor(|editor, window, cx| {
10325        editor.handle_input("\"", window, cx);
10326    });
10327    cx.assert_editor_state(
10328        &r#"
10329            let x = "ˇ"
10330        "#
10331        .unindent(),
10332    );
10333
10334    // Inserting another quotation mark. The cursor moves across the existing
10335    // automatically-inserted quotation mark.
10336    cx.update_editor(|editor, window, cx| {
10337        editor.handle_input("\"", window, cx);
10338    });
10339    cx.assert_editor_state(
10340        &r#"
10341            let x = ""ˇ
10342        "#
10343        .unindent(),
10344    );
10345
10346    // Reset
10347    cx.set_state(
10348        &r#"
10349            let x = ˇ
10350        "#
10351        .unindent(),
10352    );
10353
10354    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
10355    cx.update_editor(|editor, window, cx| {
10356        editor.handle_input("\"", window, cx);
10357        editor.handle_input(" ", window, cx);
10358        editor.move_left(&Default::default(), window, cx);
10359        editor.handle_input("\\", window, cx);
10360        editor.handle_input("\"", window, cx);
10361    });
10362    cx.assert_editor_state(
10363        &r#"
10364            let x = "\"ˇ "
10365        "#
10366        .unindent(),
10367    );
10368
10369    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
10370    // mark. Nothing is inserted.
10371    cx.update_editor(|editor, window, cx| {
10372        editor.move_right(&Default::default(), window, cx);
10373        editor.handle_input("\"", window, cx);
10374    });
10375    cx.assert_editor_state(
10376        &r#"
10377            let x = "\" "ˇ
10378        "#
10379        .unindent(),
10380    );
10381}
10382
10383#[gpui::test]
10384async fn test_surround_with_pair(cx: &mut TestAppContext) {
10385    init_test(cx, |_| {});
10386
10387    let language = Arc::new(Language::new(
10388        LanguageConfig {
10389            brackets: BracketPairConfig {
10390                pairs: vec![
10391                    BracketPair {
10392                        start: "{".to_string(),
10393                        end: "}".to_string(),
10394                        close: true,
10395                        surround: true,
10396                        newline: true,
10397                    },
10398                    BracketPair {
10399                        start: "/* ".to_string(),
10400                        end: "*/".to_string(),
10401                        close: true,
10402                        surround: true,
10403                        ..Default::default()
10404                    },
10405                ],
10406                ..Default::default()
10407            },
10408            ..Default::default()
10409        },
10410        Some(tree_sitter_rust::LANGUAGE.into()),
10411    ));
10412
10413    let text = r#"
10414        a
10415        b
10416        c
10417    "#
10418    .unindent();
10419
10420    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10421    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10422    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10423    editor
10424        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10425        .await;
10426
10427    editor.update_in(cx, |editor, window, cx| {
10428        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10429            s.select_display_ranges([
10430                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10431                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10432                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
10433            ])
10434        });
10435
10436        editor.handle_input("{", window, cx);
10437        editor.handle_input("{", window, cx);
10438        editor.handle_input("{", window, cx);
10439        assert_eq!(
10440            editor.text(cx),
10441            "
10442                {{{a}}}
10443                {{{b}}}
10444                {{{c}}}
10445            "
10446            .unindent()
10447        );
10448        assert_eq!(
10449            editor.selections.display_ranges(cx),
10450            [
10451                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
10452                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
10453                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
10454            ]
10455        );
10456
10457        editor.undo(&Undo, window, cx);
10458        editor.undo(&Undo, window, cx);
10459        editor.undo(&Undo, window, cx);
10460        assert_eq!(
10461            editor.text(cx),
10462            "
10463                a
10464                b
10465                c
10466            "
10467            .unindent()
10468        );
10469        assert_eq!(
10470            editor.selections.display_ranges(cx),
10471            [
10472                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10473                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10474                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10475            ]
10476        );
10477
10478        // Ensure inserting the first character of a multi-byte bracket pair
10479        // doesn't surround the selections with the bracket.
10480        editor.handle_input("/", window, cx);
10481        assert_eq!(
10482            editor.text(cx),
10483            "
10484                /
10485                /
10486                /
10487            "
10488            .unindent()
10489        );
10490        assert_eq!(
10491            editor.selections.display_ranges(cx),
10492            [
10493                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10494                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10495                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10496            ]
10497        );
10498
10499        editor.undo(&Undo, window, cx);
10500        assert_eq!(
10501            editor.text(cx),
10502            "
10503                a
10504                b
10505                c
10506            "
10507            .unindent()
10508        );
10509        assert_eq!(
10510            editor.selections.display_ranges(cx),
10511            [
10512                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10513                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10514                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10515            ]
10516        );
10517
10518        // Ensure inserting the last character of a multi-byte bracket pair
10519        // doesn't surround the selections with the bracket.
10520        editor.handle_input("*", window, cx);
10521        assert_eq!(
10522            editor.text(cx),
10523            "
10524                *
10525                *
10526                *
10527            "
10528            .unindent()
10529        );
10530        assert_eq!(
10531            editor.selections.display_ranges(cx),
10532            [
10533                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10534                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10535                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10536            ]
10537        );
10538    });
10539}
10540
10541#[gpui::test]
10542async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
10543    init_test(cx, |_| {});
10544
10545    let language = Arc::new(Language::new(
10546        LanguageConfig {
10547            brackets: BracketPairConfig {
10548                pairs: vec![BracketPair {
10549                    start: "{".to_string(),
10550                    end: "}".to_string(),
10551                    close: true,
10552                    surround: true,
10553                    newline: true,
10554                }],
10555                ..Default::default()
10556            },
10557            autoclose_before: "}".to_string(),
10558            ..Default::default()
10559        },
10560        Some(tree_sitter_rust::LANGUAGE.into()),
10561    ));
10562
10563    let text = r#"
10564        a
10565        b
10566        c
10567    "#
10568    .unindent();
10569
10570    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10571    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10572    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10573    editor
10574        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10575        .await;
10576
10577    editor.update_in(cx, |editor, window, cx| {
10578        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10579            s.select_ranges([
10580                Point::new(0, 1)..Point::new(0, 1),
10581                Point::new(1, 1)..Point::new(1, 1),
10582                Point::new(2, 1)..Point::new(2, 1),
10583            ])
10584        });
10585
10586        editor.handle_input("{", window, cx);
10587        editor.handle_input("{", window, cx);
10588        editor.handle_input("_", window, cx);
10589        assert_eq!(
10590            editor.text(cx),
10591            "
10592                a{{_}}
10593                b{{_}}
10594                c{{_}}
10595            "
10596            .unindent()
10597        );
10598        assert_eq!(
10599            editor.selections.ranges::<Point>(cx),
10600            [
10601                Point::new(0, 4)..Point::new(0, 4),
10602                Point::new(1, 4)..Point::new(1, 4),
10603                Point::new(2, 4)..Point::new(2, 4)
10604            ]
10605        );
10606
10607        editor.backspace(&Default::default(), window, cx);
10608        editor.backspace(&Default::default(), window, cx);
10609        assert_eq!(
10610            editor.text(cx),
10611            "
10612                a{}
10613                b{}
10614                c{}
10615            "
10616            .unindent()
10617        );
10618        assert_eq!(
10619            editor.selections.ranges::<Point>(cx),
10620            [
10621                Point::new(0, 2)..Point::new(0, 2),
10622                Point::new(1, 2)..Point::new(1, 2),
10623                Point::new(2, 2)..Point::new(2, 2)
10624            ]
10625        );
10626
10627        editor.delete_to_previous_word_start(&Default::default(), window, cx);
10628        assert_eq!(
10629            editor.text(cx),
10630            "
10631                a
10632                b
10633                c
10634            "
10635            .unindent()
10636        );
10637        assert_eq!(
10638            editor.selections.ranges::<Point>(cx),
10639            [
10640                Point::new(0, 1)..Point::new(0, 1),
10641                Point::new(1, 1)..Point::new(1, 1),
10642                Point::new(2, 1)..Point::new(2, 1)
10643            ]
10644        );
10645    });
10646}
10647
10648#[gpui::test]
10649async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
10650    init_test(cx, |settings| {
10651        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
10652    });
10653
10654    let mut cx = EditorTestContext::new(cx).await;
10655
10656    let language = Arc::new(Language::new(
10657        LanguageConfig {
10658            brackets: BracketPairConfig {
10659                pairs: vec![
10660                    BracketPair {
10661                        start: "{".to_string(),
10662                        end: "}".to_string(),
10663                        close: true,
10664                        surround: true,
10665                        newline: true,
10666                    },
10667                    BracketPair {
10668                        start: "(".to_string(),
10669                        end: ")".to_string(),
10670                        close: true,
10671                        surround: true,
10672                        newline: true,
10673                    },
10674                    BracketPair {
10675                        start: "[".to_string(),
10676                        end: "]".to_string(),
10677                        close: false,
10678                        surround: true,
10679                        newline: true,
10680                    },
10681                ],
10682                ..Default::default()
10683            },
10684            autoclose_before: "})]".to_string(),
10685            ..Default::default()
10686        },
10687        Some(tree_sitter_rust::LANGUAGE.into()),
10688    ));
10689
10690    cx.language_registry().add(language.clone());
10691    cx.update_buffer(|buffer, cx| {
10692        buffer.set_language(Some(language), cx);
10693    });
10694
10695    cx.set_state(
10696        &"
10697            {(ˇ)}
10698            [[ˇ]]
10699            {(ˇ)}
10700        "
10701        .unindent(),
10702    );
10703
10704    cx.update_editor(|editor, window, cx| {
10705        editor.backspace(&Default::default(), window, cx);
10706        editor.backspace(&Default::default(), window, cx);
10707    });
10708
10709    cx.assert_editor_state(
10710        &"
10711            ˇ
10712            ˇ]]
10713            ˇ
10714        "
10715        .unindent(),
10716    );
10717
10718    cx.update_editor(|editor, window, cx| {
10719        editor.handle_input("{", window, cx);
10720        editor.handle_input("{", window, cx);
10721        editor.move_right(&MoveRight, window, cx);
10722        editor.move_right(&MoveRight, window, cx);
10723        editor.move_left(&MoveLeft, window, cx);
10724        editor.move_left(&MoveLeft, window, cx);
10725        editor.backspace(&Default::default(), window, cx);
10726    });
10727
10728    cx.assert_editor_state(
10729        &"
10730            {ˇ}
10731            {ˇ}]]
10732            {ˇ}
10733        "
10734        .unindent(),
10735    );
10736
10737    cx.update_editor(|editor, window, cx| {
10738        editor.backspace(&Default::default(), window, cx);
10739    });
10740
10741    cx.assert_editor_state(
10742        &"
10743            ˇ
10744            ˇ]]
10745            ˇ
10746        "
10747        .unindent(),
10748    );
10749}
10750
10751#[gpui::test]
10752async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
10753    init_test(cx, |_| {});
10754
10755    let language = Arc::new(Language::new(
10756        LanguageConfig::default(),
10757        Some(tree_sitter_rust::LANGUAGE.into()),
10758    ));
10759
10760    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
10761    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10762    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10763    editor
10764        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10765        .await;
10766
10767    editor.update_in(cx, |editor, window, cx| {
10768        editor.set_auto_replace_emoji_shortcode(true);
10769
10770        editor.handle_input("Hello ", window, cx);
10771        editor.handle_input(":wave", window, cx);
10772        assert_eq!(editor.text(cx), "Hello :wave".unindent());
10773
10774        editor.handle_input(":", window, cx);
10775        assert_eq!(editor.text(cx), "Hello 👋".unindent());
10776
10777        editor.handle_input(" :smile", window, cx);
10778        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
10779
10780        editor.handle_input(":", window, cx);
10781        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
10782
10783        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
10784        editor.handle_input(":wave", window, cx);
10785        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
10786
10787        editor.handle_input(":", window, cx);
10788        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
10789
10790        editor.handle_input(":1", window, cx);
10791        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
10792
10793        editor.handle_input(":", window, cx);
10794        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
10795
10796        // Ensure shortcode does not get replaced when it is part of a word
10797        editor.handle_input(" Test:wave", window, cx);
10798        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
10799
10800        editor.handle_input(":", window, cx);
10801        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
10802
10803        editor.set_auto_replace_emoji_shortcode(false);
10804
10805        // Ensure shortcode does not get replaced when auto replace is off
10806        editor.handle_input(" :wave", window, cx);
10807        assert_eq!(
10808            editor.text(cx),
10809            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
10810        );
10811
10812        editor.handle_input(":", window, cx);
10813        assert_eq!(
10814            editor.text(cx),
10815            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
10816        );
10817    });
10818}
10819
10820#[gpui::test]
10821async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
10822    init_test(cx, |_| {});
10823
10824    let (text, insertion_ranges) = marked_text_ranges(
10825        indoc! {"
10826            ˇ
10827        "},
10828        false,
10829    );
10830
10831    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
10832    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10833
10834    _ = editor.update_in(cx, |editor, window, cx| {
10835        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
10836
10837        editor
10838            .insert_snippet(&insertion_ranges, snippet, window, cx)
10839            .unwrap();
10840
10841        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
10842            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
10843            assert_eq!(editor.text(cx), expected_text);
10844            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
10845        }
10846
10847        assert(
10848            editor,
10849            cx,
10850            indoc! {"
10851            type «» =•
10852            "},
10853        );
10854
10855        assert!(editor.context_menu_visible(), "There should be a matches");
10856    });
10857}
10858
10859#[gpui::test]
10860async fn test_snippets(cx: &mut TestAppContext) {
10861    init_test(cx, |_| {});
10862
10863    let mut cx = EditorTestContext::new(cx).await;
10864
10865    cx.set_state(indoc! {"
10866        a.ˇ b
10867        a.ˇ b
10868        a.ˇ b
10869    "});
10870
10871    cx.update_editor(|editor, window, cx| {
10872        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
10873        let insertion_ranges = editor
10874            .selections
10875            .all(cx)
10876            .iter()
10877            .map(|s| s.range())
10878            .collect::<Vec<_>>();
10879        editor
10880            .insert_snippet(&insertion_ranges, snippet, window, cx)
10881            .unwrap();
10882    });
10883
10884    cx.assert_editor_state(indoc! {"
10885        a.f(«oneˇ», two, «threeˇ») b
10886        a.f(«oneˇ», two, «threeˇ») b
10887        a.f(«oneˇ», two, «threeˇ») b
10888    "});
10889
10890    // Can't move earlier than the first tab stop
10891    cx.update_editor(|editor, window, cx| {
10892        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
10893    });
10894    cx.assert_editor_state(indoc! {"
10895        a.f(«oneˇ», two, «threeˇ») b
10896        a.f(«oneˇ», two, «threeˇ») b
10897        a.f(«oneˇ», two, «threeˇ») b
10898    "});
10899
10900    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10901    cx.assert_editor_state(indoc! {"
10902        a.f(one, «twoˇ», three) b
10903        a.f(one, «twoˇ», three) b
10904        a.f(one, «twoˇ», three) b
10905    "});
10906
10907    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
10908    cx.assert_editor_state(indoc! {"
10909        a.f(«oneˇ», two, «threeˇ») b
10910        a.f(«oneˇ», two, «threeˇ») b
10911        a.f(«oneˇ», two, «threeˇ») b
10912    "});
10913
10914    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10915    cx.assert_editor_state(indoc! {"
10916        a.f(one, «twoˇ», three) b
10917        a.f(one, «twoˇ», three) b
10918        a.f(one, «twoˇ», three) b
10919    "});
10920    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10921    cx.assert_editor_state(indoc! {"
10922        a.f(one, two, three)ˇ b
10923        a.f(one, two, three)ˇ b
10924        a.f(one, two, three)ˇ b
10925    "});
10926
10927    // As soon as the last tab stop is reached, snippet state is gone
10928    cx.update_editor(|editor, window, cx| {
10929        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
10930    });
10931    cx.assert_editor_state(indoc! {"
10932        a.f(one, two, three)ˇ b
10933        a.f(one, two, three)ˇ b
10934        a.f(one, two, three)ˇ b
10935    "});
10936}
10937
10938#[gpui::test]
10939async fn test_snippet_indentation(cx: &mut TestAppContext) {
10940    init_test(cx, |_| {});
10941
10942    let mut cx = EditorTestContext::new(cx).await;
10943
10944    cx.update_editor(|editor, window, cx| {
10945        let snippet = Snippet::parse(indoc! {"
10946            /*
10947             * Multiline comment with leading indentation
10948             *
10949             * $1
10950             */
10951            $0"})
10952        .unwrap();
10953        let insertion_ranges = editor
10954            .selections
10955            .all(cx)
10956            .iter()
10957            .map(|s| s.range())
10958            .collect::<Vec<_>>();
10959        editor
10960            .insert_snippet(&insertion_ranges, snippet, window, cx)
10961            .unwrap();
10962    });
10963
10964    cx.assert_editor_state(indoc! {"
10965        /*
10966         * Multiline comment with leading indentation
10967         *
10968         * ˇ
10969         */
10970    "});
10971
10972    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10973    cx.assert_editor_state(indoc! {"
10974        /*
10975         * Multiline comment with leading indentation
10976         *
10977         *•
10978         */
10979        ˇ"});
10980}
10981
10982#[gpui::test]
10983async fn test_document_format_during_save(cx: &mut TestAppContext) {
10984    init_test(cx, |_| {});
10985
10986    let fs = FakeFs::new(cx.executor());
10987    fs.insert_file(path!("/file.rs"), Default::default()).await;
10988
10989    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
10990
10991    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10992    language_registry.add(rust_lang());
10993    let mut fake_servers = language_registry.register_fake_lsp(
10994        "Rust",
10995        FakeLspAdapter {
10996            capabilities: lsp::ServerCapabilities {
10997                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10998                ..Default::default()
10999            },
11000            ..Default::default()
11001        },
11002    );
11003
11004    let buffer = project
11005        .update(cx, |project, cx| {
11006            project.open_local_buffer(path!("/file.rs"), cx)
11007        })
11008        .await
11009        .unwrap();
11010
11011    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11012    let (editor, cx) = cx.add_window_view(|window, cx| {
11013        build_editor_with_project(project.clone(), buffer, window, cx)
11014    });
11015    editor.update_in(cx, |editor, window, cx| {
11016        editor.set_text("one\ntwo\nthree\n", window, cx)
11017    });
11018    assert!(cx.read(|cx| editor.is_dirty(cx)));
11019
11020    cx.executor().start_waiting();
11021    let fake_server = fake_servers.next().await.unwrap();
11022
11023    {
11024        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11025            move |params, _| async move {
11026                assert_eq!(
11027                    params.text_document.uri,
11028                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11029                );
11030                assert_eq!(params.options.tab_size, 4);
11031                Ok(Some(vec![lsp::TextEdit::new(
11032                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11033                    ", ".to_string(),
11034                )]))
11035            },
11036        );
11037        let save = editor
11038            .update_in(cx, |editor, window, cx| {
11039                editor.save(
11040                    SaveOptions {
11041                        format: true,
11042                        autosave: false,
11043                    },
11044                    project.clone(),
11045                    window,
11046                    cx,
11047                )
11048            })
11049            .unwrap();
11050        cx.executor().start_waiting();
11051        save.await;
11052
11053        assert_eq!(
11054            editor.update(cx, |editor, cx| editor.text(cx)),
11055            "one, two\nthree\n"
11056        );
11057        assert!(!cx.read(|cx| editor.is_dirty(cx)));
11058    }
11059
11060    {
11061        editor.update_in(cx, |editor, window, cx| {
11062            editor.set_text("one\ntwo\nthree\n", window, cx)
11063        });
11064        assert!(cx.read(|cx| editor.is_dirty(cx)));
11065
11066        // Ensure we can still save even if formatting hangs.
11067        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11068            move |params, _| async move {
11069                assert_eq!(
11070                    params.text_document.uri,
11071                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11072                );
11073                futures::future::pending::<()>().await;
11074                unreachable!()
11075            },
11076        );
11077        let save = editor
11078            .update_in(cx, |editor, window, cx| {
11079                editor.save(
11080                    SaveOptions {
11081                        format: true,
11082                        autosave: false,
11083                    },
11084                    project.clone(),
11085                    window,
11086                    cx,
11087                )
11088            })
11089            .unwrap();
11090        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11091        cx.executor().start_waiting();
11092        save.await;
11093        assert_eq!(
11094            editor.update(cx, |editor, cx| editor.text(cx)),
11095            "one\ntwo\nthree\n"
11096        );
11097    }
11098
11099    // Set rust language override and assert overridden tabsize is sent to language server
11100    update_test_language_settings(cx, |settings| {
11101        settings.languages.0.insert(
11102            "Rust".into(),
11103            LanguageSettingsContent {
11104                tab_size: NonZeroU32::new(8),
11105                ..Default::default()
11106            },
11107        );
11108    });
11109
11110    {
11111        editor.update_in(cx, |editor, window, cx| {
11112            editor.set_text("somehting_new\n", window, cx)
11113        });
11114        assert!(cx.read(|cx| editor.is_dirty(cx)));
11115        let _formatting_request_signal = fake_server
11116            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
11117                assert_eq!(
11118                    params.text_document.uri,
11119                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11120                );
11121                assert_eq!(params.options.tab_size, 8);
11122                Ok(Some(vec![]))
11123            });
11124        let save = editor
11125            .update_in(cx, |editor, window, cx| {
11126                editor.save(
11127                    SaveOptions {
11128                        format: true,
11129                        autosave: false,
11130                    },
11131                    project.clone(),
11132                    window,
11133                    cx,
11134                )
11135            })
11136            .unwrap();
11137        cx.executor().start_waiting();
11138        save.await;
11139    }
11140}
11141
11142#[gpui::test]
11143async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
11144    init_test(cx, |settings| {
11145        settings.defaults.ensure_final_newline_on_save = Some(false);
11146    });
11147
11148    let fs = FakeFs::new(cx.executor());
11149    fs.insert_file(path!("/file.txt"), "foo".into()).await;
11150
11151    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
11152
11153    let buffer = project
11154        .update(cx, |project, cx| {
11155            project.open_local_buffer(path!("/file.txt"), cx)
11156        })
11157        .await
11158        .unwrap();
11159
11160    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11161    let (editor, cx) = cx.add_window_view(|window, cx| {
11162        build_editor_with_project(project.clone(), buffer, window, cx)
11163    });
11164    editor.update_in(cx, |editor, window, cx| {
11165        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
11166            s.select_ranges([0..0])
11167        });
11168    });
11169    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11170
11171    editor.update_in(cx, |editor, window, cx| {
11172        editor.handle_input("\n", window, cx)
11173    });
11174    cx.run_until_parked();
11175    save(&editor, &project, cx).await;
11176    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11177
11178    editor.update_in(cx, |editor, window, cx| {
11179        editor.undo(&Default::default(), window, cx);
11180    });
11181    save(&editor, &project, cx).await;
11182    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11183
11184    editor.update_in(cx, |editor, window, cx| {
11185        editor.redo(&Default::default(), window, cx);
11186    });
11187    cx.run_until_parked();
11188    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11189
11190    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
11191        let save = editor
11192            .update_in(cx, |editor, window, cx| {
11193                editor.save(
11194                    SaveOptions {
11195                        format: true,
11196                        autosave: false,
11197                    },
11198                    project.clone(),
11199                    window,
11200                    cx,
11201                )
11202            })
11203            .unwrap();
11204        cx.executor().start_waiting();
11205        save.await;
11206        assert!(!cx.read(|cx| editor.is_dirty(cx)));
11207    }
11208}
11209
11210#[gpui::test]
11211async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
11212    init_test(cx, |_| {});
11213
11214    let cols = 4;
11215    let rows = 10;
11216    let sample_text_1 = sample_text(rows, cols, 'a');
11217    assert_eq!(
11218        sample_text_1,
11219        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11220    );
11221    let sample_text_2 = sample_text(rows, cols, 'l');
11222    assert_eq!(
11223        sample_text_2,
11224        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11225    );
11226    let sample_text_3 = sample_text(rows, cols, 'v');
11227    assert_eq!(
11228        sample_text_3,
11229        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11230    );
11231
11232    let fs = FakeFs::new(cx.executor());
11233    fs.insert_tree(
11234        path!("/a"),
11235        json!({
11236            "main.rs": sample_text_1,
11237            "other.rs": sample_text_2,
11238            "lib.rs": sample_text_3,
11239        }),
11240    )
11241    .await;
11242
11243    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11244    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11245    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11246
11247    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11248    language_registry.add(rust_lang());
11249    let mut fake_servers = language_registry.register_fake_lsp(
11250        "Rust",
11251        FakeLspAdapter {
11252            capabilities: lsp::ServerCapabilities {
11253                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11254                ..Default::default()
11255            },
11256            ..Default::default()
11257        },
11258    );
11259
11260    let worktree = project.update(cx, |project, cx| {
11261        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
11262        assert_eq!(worktrees.len(), 1);
11263        worktrees.pop().unwrap()
11264    });
11265    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11266
11267    let buffer_1 = project
11268        .update(cx, |project, cx| {
11269            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
11270        })
11271        .await
11272        .unwrap();
11273    let buffer_2 = project
11274        .update(cx, |project, cx| {
11275            project.open_buffer((worktree_id, rel_path("other.rs")), cx)
11276        })
11277        .await
11278        .unwrap();
11279    let buffer_3 = project
11280        .update(cx, |project, cx| {
11281            project.open_buffer((worktree_id, rel_path("lib.rs")), cx)
11282        })
11283        .await
11284        .unwrap();
11285
11286    let multi_buffer = cx.new(|cx| {
11287        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11288        multi_buffer.push_excerpts(
11289            buffer_1.clone(),
11290            [
11291                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11292                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11293                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11294            ],
11295            cx,
11296        );
11297        multi_buffer.push_excerpts(
11298            buffer_2.clone(),
11299            [
11300                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11301                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11302                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11303            ],
11304            cx,
11305        );
11306        multi_buffer.push_excerpts(
11307            buffer_3.clone(),
11308            [
11309                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11310                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11311                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11312            ],
11313            cx,
11314        );
11315        multi_buffer
11316    });
11317    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
11318        Editor::new(
11319            EditorMode::full(),
11320            multi_buffer,
11321            Some(project.clone()),
11322            window,
11323            cx,
11324        )
11325    });
11326
11327    multi_buffer_editor.update_in(cx, |editor, window, cx| {
11328        editor.change_selections(
11329            SelectionEffects::scroll(Autoscroll::Next),
11330            window,
11331            cx,
11332            |s| s.select_ranges(Some(1..2)),
11333        );
11334        editor.insert("|one|two|three|", window, cx);
11335    });
11336    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
11337    multi_buffer_editor.update_in(cx, |editor, window, cx| {
11338        editor.change_selections(
11339            SelectionEffects::scroll(Autoscroll::Next),
11340            window,
11341            cx,
11342            |s| s.select_ranges(Some(60..70)),
11343        );
11344        editor.insert("|four|five|six|", window, cx);
11345    });
11346    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
11347
11348    // First two buffers should be edited, but not the third one.
11349    assert_eq!(
11350        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11351        "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}",
11352    );
11353    buffer_1.update(cx, |buffer, _| {
11354        assert!(buffer.is_dirty());
11355        assert_eq!(
11356            buffer.text(),
11357            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
11358        )
11359    });
11360    buffer_2.update(cx, |buffer, _| {
11361        assert!(buffer.is_dirty());
11362        assert_eq!(
11363            buffer.text(),
11364            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
11365        )
11366    });
11367    buffer_3.update(cx, |buffer, _| {
11368        assert!(!buffer.is_dirty());
11369        assert_eq!(buffer.text(), sample_text_3,)
11370    });
11371    cx.executor().run_until_parked();
11372
11373    cx.executor().start_waiting();
11374    let save = multi_buffer_editor
11375        .update_in(cx, |editor, window, cx| {
11376            editor.save(
11377                SaveOptions {
11378                    format: true,
11379                    autosave: false,
11380                },
11381                project.clone(),
11382                window,
11383                cx,
11384            )
11385        })
11386        .unwrap();
11387
11388    let fake_server = fake_servers.next().await.unwrap();
11389    fake_server
11390        .server
11391        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
11392            Ok(Some(vec![lsp::TextEdit::new(
11393                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11394                format!("[{} formatted]", params.text_document.uri),
11395            )]))
11396        })
11397        .detach();
11398    save.await;
11399
11400    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
11401    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
11402    assert_eq!(
11403        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11404        uri!(
11405            "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}"
11406        ),
11407    );
11408    buffer_1.update(cx, |buffer, _| {
11409        assert!(!buffer.is_dirty());
11410        assert_eq!(
11411            buffer.text(),
11412            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
11413        )
11414    });
11415    buffer_2.update(cx, |buffer, _| {
11416        assert!(!buffer.is_dirty());
11417        assert_eq!(
11418            buffer.text(),
11419            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
11420        )
11421    });
11422    buffer_3.update(cx, |buffer, _| {
11423        assert!(!buffer.is_dirty());
11424        assert_eq!(buffer.text(), sample_text_3,)
11425    });
11426}
11427
11428#[gpui::test]
11429async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
11430    init_test(cx, |_| {});
11431
11432    let fs = FakeFs::new(cx.executor());
11433    fs.insert_tree(
11434        path!("/dir"),
11435        json!({
11436            "file1.rs": "fn main() { println!(\"hello\"); }",
11437            "file2.rs": "fn test() { println!(\"test\"); }",
11438            "file3.rs": "fn other() { println!(\"other\"); }\n",
11439        }),
11440    )
11441    .await;
11442
11443    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
11444    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11445    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11446
11447    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11448    language_registry.add(rust_lang());
11449
11450    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
11451    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11452
11453    // Open three buffers
11454    let buffer_1 = project
11455        .update(cx, |project, cx| {
11456            project.open_buffer((worktree_id, rel_path("file1.rs")), cx)
11457        })
11458        .await
11459        .unwrap();
11460    let buffer_2 = project
11461        .update(cx, |project, cx| {
11462            project.open_buffer((worktree_id, rel_path("file2.rs")), cx)
11463        })
11464        .await
11465        .unwrap();
11466    let buffer_3 = project
11467        .update(cx, |project, cx| {
11468            project.open_buffer((worktree_id, rel_path("file3.rs")), cx)
11469        })
11470        .await
11471        .unwrap();
11472
11473    // Create a multi-buffer with all three buffers
11474    let multi_buffer = cx.new(|cx| {
11475        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11476        multi_buffer.push_excerpts(
11477            buffer_1.clone(),
11478            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11479            cx,
11480        );
11481        multi_buffer.push_excerpts(
11482            buffer_2.clone(),
11483            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11484            cx,
11485        );
11486        multi_buffer.push_excerpts(
11487            buffer_3.clone(),
11488            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11489            cx,
11490        );
11491        multi_buffer
11492    });
11493
11494    let editor = cx.new_window_entity(|window, cx| {
11495        Editor::new(
11496            EditorMode::full(),
11497            multi_buffer,
11498            Some(project.clone()),
11499            window,
11500            cx,
11501        )
11502    });
11503
11504    // Edit only the first buffer
11505    editor.update_in(cx, |editor, window, cx| {
11506        editor.change_selections(
11507            SelectionEffects::scroll(Autoscroll::Next),
11508            window,
11509            cx,
11510            |s| s.select_ranges(Some(10..10)),
11511        );
11512        editor.insert("// edited", window, cx);
11513    });
11514
11515    // Verify that only buffer 1 is dirty
11516    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
11517    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11518    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11519
11520    // Get write counts after file creation (files were created with initial content)
11521    // We expect each file to have been written once during creation
11522    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
11523    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
11524    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
11525
11526    // Perform autosave
11527    let save_task = editor.update_in(cx, |editor, window, cx| {
11528        editor.save(
11529            SaveOptions {
11530                format: true,
11531                autosave: true,
11532            },
11533            project.clone(),
11534            window,
11535            cx,
11536        )
11537    });
11538    save_task.await.unwrap();
11539
11540    // Only the dirty buffer should have been saved
11541    assert_eq!(
11542        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
11543        1,
11544        "Buffer 1 was dirty, so it should have been written once during autosave"
11545    );
11546    assert_eq!(
11547        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
11548        0,
11549        "Buffer 2 was clean, so it should not have been written during autosave"
11550    );
11551    assert_eq!(
11552        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
11553        0,
11554        "Buffer 3 was clean, so it should not have been written during autosave"
11555    );
11556
11557    // Verify buffer states after autosave
11558    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11559    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11560    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11561
11562    // Now perform a manual save (format = true)
11563    let save_task = editor.update_in(cx, |editor, window, cx| {
11564        editor.save(
11565            SaveOptions {
11566                format: true,
11567                autosave: false,
11568            },
11569            project.clone(),
11570            window,
11571            cx,
11572        )
11573    });
11574    save_task.await.unwrap();
11575
11576    // During manual save, clean buffers don't get written to disk
11577    // They just get did_save called for language server notifications
11578    assert_eq!(
11579        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
11580        1,
11581        "Buffer 1 should only have been written once total (during autosave, not manual save)"
11582    );
11583    assert_eq!(
11584        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
11585        0,
11586        "Buffer 2 should not have been written at all"
11587    );
11588    assert_eq!(
11589        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
11590        0,
11591        "Buffer 3 should not have been written at all"
11592    );
11593}
11594
11595async fn setup_range_format_test(
11596    cx: &mut TestAppContext,
11597) -> (
11598    Entity<Project>,
11599    Entity<Editor>,
11600    &mut gpui::VisualTestContext,
11601    lsp::FakeLanguageServer,
11602) {
11603    init_test(cx, |_| {});
11604
11605    let fs = FakeFs::new(cx.executor());
11606    fs.insert_file(path!("/file.rs"), Default::default()).await;
11607
11608    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11609
11610    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11611    language_registry.add(rust_lang());
11612    let mut fake_servers = language_registry.register_fake_lsp(
11613        "Rust",
11614        FakeLspAdapter {
11615            capabilities: lsp::ServerCapabilities {
11616                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
11617                ..lsp::ServerCapabilities::default()
11618            },
11619            ..FakeLspAdapter::default()
11620        },
11621    );
11622
11623    let buffer = project
11624        .update(cx, |project, cx| {
11625            project.open_local_buffer(path!("/file.rs"), cx)
11626        })
11627        .await
11628        .unwrap();
11629
11630    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11631    let (editor, cx) = cx.add_window_view(|window, cx| {
11632        build_editor_with_project(project.clone(), buffer, window, cx)
11633    });
11634
11635    cx.executor().start_waiting();
11636    let fake_server = fake_servers.next().await.unwrap();
11637
11638    (project, editor, cx, fake_server)
11639}
11640
11641#[gpui::test]
11642async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
11643    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11644
11645    editor.update_in(cx, |editor, window, cx| {
11646        editor.set_text("one\ntwo\nthree\n", window, cx)
11647    });
11648    assert!(cx.read(|cx| editor.is_dirty(cx)));
11649
11650    let save = editor
11651        .update_in(cx, |editor, window, cx| {
11652            editor.save(
11653                SaveOptions {
11654                    format: true,
11655                    autosave: false,
11656                },
11657                project.clone(),
11658                window,
11659                cx,
11660            )
11661        })
11662        .unwrap();
11663    fake_server
11664        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
11665            assert_eq!(
11666                params.text_document.uri,
11667                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11668            );
11669            assert_eq!(params.options.tab_size, 4);
11670            Ok(Some(vec![lsp::TextEdit::new(
11671                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11672                ", ".to_string(),
11673            )]))
11674        })
11675        .next()
11676        .await;
11677    cx.executor().start_waiting();
11678    save.await;
11679    assert_eq!(
11680        editor.update(cx, |editor, cx| editor.text(cx)),
11681        "one, two\nthree\n"
11682    );
11683    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11684}
11685
11686#[gpui::test]
11687async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
11688    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11689
11690    editor.update_in(cx, |editor, window, cx| {
11691        editor.set_text("one\ntwo\nthree\n", window, cx)
11692    });
11693    assert!(cx.read(|cx| editor.is_dirty(cx)));
11694
11695    // Test that save still works when formatting hangs
11696    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
11697        move |params, _| async move {
11698            assert_eq!(
11699                params.text_document.uri,
11700                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11701            );
11702            futures::future::pending::<()>().await;
11703            unreachable!()
11704        },
11705    );
11706    let save = editor
11707        .update_in(cx, |editor, window, cx| {
11708            editor.save(
11709                SaveOptions {
11710                    format: true,
11711                    autosave: false,
11712                },
11713                project.clone(),
11714                window,
11715                cx,
11716            )
11717        })
11718        .unwrap();
11719    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11720    cx.executor().start_waiting();
11721    save.await;
11722    assert_eq!(
11723        editor.update(cx, |editor, cx| editor.text(cx)),
11724        "one\ntwo\nthree\n"
11725    );
11726    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11727}
11728
11729#[gpui::test]
11730async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
11731    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11732
11733    // Buffer starts clean, no formatting should be requested
11734    let save = editor
11735        .update_in(cx, |editor, window, cx| {
11736            editor.save(
11737                SaveOptions {
11738                    format: false,
11739                    autosave: false,
11740                },
11741                project.clone(),
11742                window,
11743                cx,
11744            )
11745        })
11746        .unwrap();
11747    let _pending_format_request = fake_server
11748        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
11749            panic!("Should not be invoked");
11750        })
11751        .next();
11752    cx.executor().start_waiting();
11753    save.await;
11754    cx.run_until_parked();
11755}
11756
11757#[gpui::test]
11758async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
11759    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11760
11761    // Set Rust language override and assert overridden tabsize is sent to language server
11762    update_test_language_settings(cx, |settings| {
11763        settings.languages.0.insert(
11764            "Rust".into(),
11765            LanguageSettingsContent {
11766                tab_size: NonZeroU32::new(8),
11767                ..Default::default()
11768            },
11769        );
11770    });
11771
11772    editor.update_in(cx, |editor, window, cx| {
11773        editor.set_text("something_new\n", window, cx)
11774    });
11775    assert!(cx.read(|cx| editor.is_dirty(cx)));
11776    let save = editor
11777        .update_in(cx, |editor, window, cx| {
11778            editor.save(
11779                SaveOptions {
11780                    format: true,
11781                    autosave: false,
11782                },
11783                project.clone(),
11784                window,
11785                cx,
11786            )
11787        })
11788        .unwrap();
11789    fake_server
11790        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
11791            assert_eq!(
11792                params.text_document.uri,
11793                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11794            );
11795            assert_eq!(params.options.tab_size, 8);
11796            Ok(Some(Vec::new()))
11797        })
11798        .next()
11799        .await;
11800    save.await;
11801}
11802
11803#[gpui::test]
11804async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
11805    init_test(cx, |settings| {
11806        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
11807            Formatter::LanguageServer { name: None },
11808        )))
11809    });
11810
11811    let fs = FakeFs::new(cx.executor());
11812    fs.insert_file(path!("/file.rs"), Default::default()).await;
11813
11814    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11815
11816    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11817    language_registry.add(Arc::new(Language::new(
11818        LanguageConfig {
11819            name: "Rust".into(),
11820            matcher: LanguageMatcher {
11821                path_suffixes: vec!["rs".to_string()],
11822                ..Default::default()
11823            },
11824            ..LanguageConfig::default()
11825        },
11826        Some(tree_sitter_rust::LANGUAGE.into()),
11827    )));
11828    update_test_language_settings(cx, |settings| {
11829        // Enable Prettier formatting for the same buffer, and ensure
11830        // LSP is called instead of Prettier.
11831        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
11832    });
11833    let mut fake_servers = language_registry.register_fake_lsp(
11834        "Rust",
11835        FakeLspAdapter {
11836            capabilities: lsp::ServerCapabilities {
11837                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11838                ..Default::default()
11839            },
11840            ..Default::default()
11841        },
11842    );
11843
11844    let buffer = project
11845        .update(cx, |project, cx| {
11846            project.open_local_buffer(path!("/file.rs"), cx)
11847        })
11848        .await
11849        .unwrap();
11850
11851    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11852    let (editor, cx) = cx.add_window_view(|window, cx| {
11853        build_editor_with_project(project.clone(), buffer, window, cx)
11854    });
11855    editor.update_in(cx, |editor, window, cx| {
11856        editor.set_text("one\ntwo\nthree\n", window, cx)
11857    });
11858
11859    cx.executor().start_waiting();
11860    let fake_server = fake_servers.next().await.unwrap();
11861
11862    let format = editor
11863        .update_in(cx, |editor, window, cx| {
11864            editor.perform_format(
11865                project.clone(),
11866                FormatTrigger::Manual,
11867                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
11868                window,
11869                cx,
11870            )
11871        })
11872        .unwrap();
11873    fake_server
11874        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
11875            assert_eq!(
11876                params.text_document.uri,
11877                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11878            );
11879            assert_eq!(params.options.tab_size, 4);
11880            Ok(Some(vec![lsp::TextEdit::new(
11881                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11882                ", ".to_string(),
11883            )]))
11884        })
11885        .next()
11886        .await;
11887    cx.executor().start_waiting();
11888    format.await;
11889    assert_eq!(
11890        editor.update(cx, |editor, cx| editor.text(cx)),
11891        "one, two\nthree\n"
11892    );
11893
11894    editor.update_in(cx, |editor, window, cx| {
11895        editor.set_text("one\ntwo\nthree\n", window, cx)
11896    });
11897    // Ensure we don't lock if formatting hangs.
11898    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11899        move |params, _| async move {
11900            assert_eq!(
11901                params.text_document.uri,
11902                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11903            );
11904            futures::future::pending::<()>().await;
11905            unreachable!()
11906        },
11907    );
11908    let format = editor
11909        .update_in(cx, |editor, window, cx| {
11910            editor.perform_format(
11911                project,
11912                FormatTrigger::Manual,
11913                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
11914                window,
11915                cx,
11916            )
11917        })
11918        .unwrap();
11919    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11920    cx.executor().start_waiting();
11921    format.await;
11922    assert_eq!(
11923        editor.update(cx, |editor, cx| editor.text(cx)),
11924        "one\ntwo\nthree\n"
11925    );
11926}
11927
11928#[gpui::test]
11929async fn test_multiple_formatters(cx: &mut TestAppContext) {
11930    init_test(cx, |settings| {
11931        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
11932        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
11933            Formatter::LanguageServer { name: None },
11934            Formatter::CodeAction("code-action-1".into()),
11935            Formatter::CodeAction("code-action-2".into()),
11936        ])))
11937    });
11938
11939    let fs = FakeFs::new(cx.executor());
11940    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
11941        .await;
11942
11943    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11944    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11945    language_registry.add(rust_lang());
11946
11947    let mut fake_servers = language_registry.register_fake_lsp(
11948        "Rust",
11949        FakeLspAdapter {
11950            capabilities: lsp::ServerCapabilities {
11951                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11952                execute_command_provider: Some(lsp::ExecuteCommandOptions {
11953                    commands: vec!["the-command-for-code-action-1".into()],
11954                    ..Default::default()
11955                }),
11956                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
11957                ..Default::default()
11958            },
11959            ..Default::default()
11960        },
11961    );
11962
11963    let buffer = project
11964        .update(cx, |project, cx| {
11965            project.open_local_buffer(path!("/file.rs"), cx)
11966        })
11967        .await
11968        .unwrap();
11969
11970    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11971    let (editor, cx) = cx.add_window_view(|window, cx| {
11972        build_editor_with_project(project.clone(), buffer, window, cx)
11973    });
11974
11975    cx.executor().start_waiting();
11976
11977    let fake_server = fake_servers.next().await.unwrap();
11978    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11979        move |_params, _| async move {
11980            Ok(Some(vec![lsp::TextEdit::new(
11981                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
11982                "applied-formatting\n".to_string(),
11983            )]))
11984        },
11985    );
11986    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
11987        move |params, _| async move {
11988            let requested_code_actions = params.context.only.expect("Expected code action request");
11989            assert_eq!(requested_code_actions.len(), 1);
11990
11991            let uri = lsp::Uri::from_file_path(path!("/file.rs")).unwrap();
11992            let code_action = match requested_code_actions[0].as_str() {
11993                "code-action-1" => lsp::CodeAction {
11994                    kind: Some("code-action-1".into()),
11995                    edit: Some(lsp::WorkspaceEdit::new(
11996                        [(
11997                            uri,
11998                            vec![lsp::TextEdit::new(
11999                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12000                                "applied-code-action-1-edit\n".to_string(),
12001                            )],
12002                        )]
12003                        .into_iter()
12004                        .collect(),
12005                    )),
12006                    command: Some(lsp::Command {
12007                        command: "the-command-for-code-action-1".into(),
12008                        ..Default::default()
12009                    }),
12010                    ..Default::default()
12011                },
12012                "code-action-2" => lsp::CodeAction {
12013                    kind: Some("code-action-2".into()),
12014                    edit: Some(lsp::WorkspaceEdit::new(
12015                        [(
12016                            uri,
12017                            vec![lsp::TextEdit::new(
12018                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12019                                "applied-code-action-2-edit\n".to_string(),
12020                            )],
12021                        )]
12022                        .into_iter()
12023                        .collect(),
12024                    )),
12025                    ..Default::default()
12026                },
12027                req => panic!("Unexpected code action request: {:?}", req),
12028            };
12029            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
12030                code_action,
12031            )]))
12032        },
12033    );
12034
12035    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
12036        move |params, _| async move { Ok(params) }
12037    });
12038
12039    let command_lock = Arc::new(futures::lock::Mutex::new(()));
12040    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
12041        let fake = fake_server.clone();
12042        let lock = command_lock.clone();
12043        move |params, _| {
12044            assert_eq!(params.command, "the-command-for-code-action-1");
12045            let fake = fake.clone();
12046            let lock = lock.clone();
12047            async move {
12048                lock.lock().await;
12049                fake.server
12050                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
12051                        label: None,
12052                        edit: lsp::WorkspaceEdit {
12053                            changes: Some(
12054                                [(
12055                                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
12056                                    vec![lsp::TextEdit {
12057                                        range: lsp::Range::new(
12058                                            lsp::Position::new(0, 0),
12059                                            lsp::Position::new(0, 0),
12060                                        ),
12061                                        new_text: "applied-code-action-1-command\n".into(),
12062                                    }],
12063                                )]
12064                                .into_iter()
12065                                .collect(),
12066                            ),
12067                            ..Default::default()
12068                        },
12069                    })
12070                    .await
12071                    .into_response()
12072                    .unwrap();
12073                Ok(Some(json!(null)))
12074            }
12075        }
12076    });
12077
12078    cx.executor().start_waiting();
12079    editor
12080        .update_in(cx, |editor, window, cx| {
12081            editor.perform_format(
12082                project.clone(),
12083                FormatTrigger::Manual,
12084                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12085                window,
12086                cx,
12087            )
12088        })
12089        .unwrap()
12090        .await;
12091    editor.update(cx, |editor, cx| {
12092        assert_eq!(
12093            editor.text(cx),
12094            r#"
12095                applied-code-action-2-edit
12096                applied-code-action-1-command
12097                applied-code-action-1-edit
12098                applied-formatting
12099                one
12100                two
12101                three
12102            "#
12103            .unindent()
12104        );
12105    });
12106
12107    editor.update_in(cx, |editor, window, cx| {
12108        editor.undo(&Default::default(), window, cx);
12109        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12110    });
12111
12112    // Perform a manual edit while waiting for an LSP command
12113    // that's being run as part of a formatting code action.
12114    let lock_guard = command_lock.lock().await;
12115    let format = editor
12116        .update_in(cx, |editor, window, cx| {
12117            editor.perform_format(
12118                project.clone(),
12119                FormatTrigger::Manual,
12120                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12121                window,
12122                cx,
12123            )
12124        })
12125        .unwrap();
12126    cx.run_until_parked();
12127    editor.update(cx, |editor, cx| {
12128        assert_eq!(
12129            editor.text(cx),
12130            r#"
12131                applied-code-action-1-edit
12132                applied-formatting
12133                one
12134                two
12135                three
12136            "#
12137            .unindent()
12138        );
12139
12140        editor.buffer.update(cx, |buffer, cx| {
12141            let ix = buffer.len(cx);
12142            buffer.edit([(ix..ix, "edited\n")], None, cx);
12143        });
12144    });
12145
12146    // Allow the LSP command to proceed. Because the buffer was edited,
12147    // the second code action will not be run.
12148    drop(lock_guard);
12149    format.await;
12150    editor.update_in(cx, |editor, window, cx| {
12151        assert_eq!(
12152            editor.text(cx),
12153            r#"
12154                applied-code-action-1-command
12155                applied-code-action-1-edit
12156                applied-formatting
12157                one
12158                two
12159                three
12160                edited
12161            "#
12162            .unindent()
12163        );
12164
12165        // The manual edit is undone first, because it is the last thing the user did
12166        // (even though the command completed afterwards).
12167        editor.undo(&Default::default(), window, cx);
12168        assert_eq!(
12169            editor.text(cx),
12170            r#"
12171                applied-code-action-1-command
12172                applied-code-action-1-edit
12173                applied-formatting
12174                one
12175                two
12176                three
12177            "#
12178            .unindent()
12179        );
12180
12181        // All the formatting (including the command, which completed after the manual edit)
12182        // is undone together.
12183        editor.undo(&Default::default(), window, cx);
12184        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12185    });
12186}
12187
12188#[gpui::test]
12189async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
12190    init_test(cx, |settings| {
12191        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
12192            Formatter::LanguageServer { name: None },
12193        ])))
12194    });
12195
12196    let fs = FakeFs::new(cx.executor());
12197    fs.insert_file(path!("/file.ts"), Default::default()).await;
12198
12199    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12200
12201    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12202    language_registry.add(Arc::new(Language::new(
12203        LanguageConfig {
12204            name: "TypeScript".into(),
12205            matcher: LanguageMatcher {
12206                path_suffixes: vec!["ts".to_string()],
12207                ..Default::default()
12208            },
12209            ..LanguageConfig::default()
12210        },
12211        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12212    )));
12213    update_test_language_settings(cx, |settings| {
12214        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
12215    });
12216    let mut fake_servers = language_registry.register_fake_lsp(
12217        "TypeScript",
12218        FakeLspAdapter {
12219            capabilities: lsp::ServerCapabilities {
12220                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
12221                ..Default::default()
12222            },
12223            ..Default::default()
12224        },
12225    );
12226
12227    let buffer = project
12228        .update(cx, |project, cx| {
12229            project.open_local_buffer(path!("/file.ts"), cx)
12230        })
12231        .await
12232        .unwrap();
12233
12234    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12235    let (editor, cx) = cx.add_window_view(|window, cx| {
12236        build_editor_with_project(project.clone(), buffer, window, cx)
12237    });
12238    editor.update_in(cx, |editor, window, cx| {
12239        editor.set_text(
12240            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12241            window,
12242            cx,
12243        )
12244    });
12245
12246    cx.executor().start_waiting();
12247    let fake_server = fake_servers.next().await.unwrap();
12248
12249    let format = editor
12250        .update_in(cx, |editor, window, cx| {
12251            editor.perform_code_action_kind(
12252                project.clone(),
12253                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12254                window,
12255                cx,
12256            )
12257        })
12258        .unwrap();
12259    fake_server
12260        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
12261            assert_eq!(
12262                params.text_document.uri,
12263                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12264            );
12265            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
12266                lsp::CodeAction {
12267                    title: "Organize Imports".to_string(),
12268                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
12269                    edit: Some(lsp::WorkspaceEdit {
12270                        changes: Some(
12271                            [(
12272                                params.text_document.uri.clone(),
12273                                vec![lsp::TextEdit::new(
12274                                    lsp::Range::new(
12275                                        lsp::Position::new(1, 0),
12276                                        lsp::Position::new(2, 0),
12277                                    ),
12278                                    "".to_string(),
12279                                )],
12280                            )]
12281                            .into_iter()
12282                            .collect(),
12283                        ),
12284                        ..Default::default()
12285                    }),
12286                    ..Default::default()
12287                },
12288            )]))
12289        })
12290        .next()
12291        .await;
12292    cx.executor().start_waiting();
12293    format.await;
12294    assert_eq!(
12295        editor.update(cx, |editor, cx| editor.text(cx)),
12296        "import { a } from 'module';\n\nconst x = a;\n"
12297    );
12298
12299    editor.update_in(cx, |editor, window, cx| {
12300        editor.set_text(
12301            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12302            window,
12303            cx,
12304        )
12305    });
12306    // Ensure we don't lock if code action hangs.
12307    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
12308        move |params, _| async move {
12309            assert_eq!(
12310                params.text_document.uri,
12311                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12312            );
12313            futures::future::pending::<()>().await;
12314            unreachable!()
12315        },
12316    );
12317    let format = editor
12318        .update_in(cx, |editor, window, cx| {
12319            editor.perform_code_action_kind(
12320                project,
12321                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12322                window,
12323                cx,
12324            )
12325        })
12326        .unwrap();
12327    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
12328    cx.executor().start_waiting();
12329    format.await;
12330    assert_eq!(
12331        editor.update(cx, |editor, cx| editor.text(cx)),
12332        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
12333    );
12334}
12335
12336#[gpui::test]
12337async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
12338    init_test(cx, |_| {});
12339
12340    let mut cx = EditorLspTestContext::new_rust(
12341        lsp::ServerCapabilities {
12342            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12343            ..Default::default()
12344        },
12345        cx,
12346    )
12347    .await;
12348
12349    cx.set_state(indoc! {"
12350        one.twoˇ
12351    "});
12352
12353    // The format request takes a long time. When it completes, it inserts
12354    // a newline and an indent before the `.`
12355    cx.lsp
12356        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
12357            let executor = cx.background_executor().clone();
12358            async move {
12359                executor.timer(Duration::from_millis(100)).await;
12360                Ok(Some(vec![lsp::TextEdit {
12361                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
12362                    new_text: "\n    ".into(),
12363                }]))
12364            }
12365        });
12366
12367    // Submit a format request.
12368    let format_1 = cx
12369        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12370        .unwrap();
12371    cx.executor().run_until_parked();
12372
12373    // Submit a second format request.
12374    let format_2 = cx
12375        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12376        .unwrap();
12377    cx.executor().run_until_parked();
12378
12379    // Wait for both format requests to complete
12380    cx.executor().advance_clock(Duration::from_millis(200));
12381    cx.executor().start_waiting();
12382    format_1.await.unwrap();
12383    cx.executor().start_waiting();
12384    format_2.await.unwrap();
12385
12386    // The formatting edits only happens once.
12387    cx.assert_editor_state(indoc! {"
12388        one
12389            .twoˇ
12390    "});
12391}
12392
12393#[gpui::test]
12394async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
12395    init_test(cx, |settings| {
12396        settings.defaults.formatter = Some(SelectedFormatter::Auto)
12397    });
12398
12399    let mut cx = EditorLspTestContext::new_rust(
12400        lsp::ServerCapabilities {
12401            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12402            ..Default::default()
12403        },
12404        cx,
12405    )
12406    .await;
12407
12408    // Set up a buffer white some trailing whitespace and no trailing newline.
12409    cx.set_state(
12410        &[
12411            "one ",   //
12412            "twoˇ",   //
12413            "three ", //
12414            "four",   //
12415        ]
12416        .join("\n"),
12417    );
12418
12419    // Submit a format request.
12420    let format = cx
12421        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12422        .unwrap();
12423
12424    // Record which buffer changes have been sent to the language server
12425    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
12426    cx.lsp
12427        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
12428            let buffer_changes = buffer_changes.clone();
12429            move |params, _| {
12430                buffer_changes.lock().extend(
12431                    params
12432                        .content_changes
12433                        .into_iter()
12434                        .map(|e| (e.range.unwrap(), e.text)),
12435                );
12436            }
12437        });
12438
12439    // Handle formatting requests to the language server.
12440    cx.lsp
12441        .set_request_handler::<lsp::request::Formatting, _, _>({
12442            let buffer_changes = buffer_changes.clone();
12443            move |_, _| {
12444                // When formatting is requested, trailing whitespace has already been stripped,
12445                // and the trailing newline has already been added.
12446                assert_eq!(
12447                    &buffer_changes.lock()[1..],
12448                    &[
12449                        (
12450                            lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
12451                            "".into()
12452                        ),
12453                        (
12454                            lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
12455                            "".into()
12456                        ),
12457                        (
12458                            lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
12459                            "\n".into()
12460                        ),
12461                    ]
12462                );
12463
12464                // Insert blank lines between each line of the buffer.
12465                async move {
12466                    Ok(Some(vec![
12467                        lsp::TextEdit {
12468                            range: lsp::Range::new(
12469                                lsp::Position::new(1, 0),
12470                                lsp::Position::new(1, 0),
12471                            ),
12472                            new_text: "\n".into(),
12473                        },
12474                        lsp::TextEdit {
12475                            range: lsp::Range::new(
12476                                lsp::Position::new(2, 0),
12477                                lsp::Position::new(2, 0),
12478                            ),
12479                            new_text: "\n".into(),
12480                        },
12481                    ]))
12482                }
12483            }
12484        });
12485
12486    // After formatting the buffer, the trailing whitespace is stripped,
12487    // a newline is appended, and the edits provided by the language server
12488    // have been applied.
12489    format.await.unwrap();
12490    cx.assert_editor_state(
12491        &[
12492            "one",   //
12493            "",      //
12494            "twoˇ",  //
12495            "",      //
12496            "three", //
12497            "four",  //
12498            "",      //
12499        ]
12500        .join("\n"),
12501    );
12502
12503    // Undoing the formatting undoes the trailing whitespace removal, the
12504    // trailing newline, and the LSP edits.
12505    cx.update_buffer(|buffer, cx| buffer.undo(cx));
12506    cx.assert_editor_state(
12507        &[
12508            "one ",   //
12509            "twoˇ",   //
12510            "three ", //
12511            "four",   //
12512        ]
12513        .join("\n"),
12514    );
12515}
12516
12517#[gpui::test]
12518async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
12519    cx: &mut TestAppContext,
12520) {
12521    init_test(cx, |_| {});
12522
12523    cx.update(|cx| {
12524        cx.update_global::<SettingsStore, _>(|settings, cx| {
12525            settings.update_user_settings(cx, |settings| {
12526                settings.editor.auto_signature_help = Some(true);
12527            });
12528        });
12529    });
12530
12531    let mut cx = EditorLspTestContext::new_rust(
12532        lsp::ServerCapabilities {
12533            signature_help_provider: Some(lsp::SignatureHelpOptions {
12534                ..Default::default()
12535            }),
12536            ..Default::default()
12537        },
12538        cx,
12539    )
12540    .await;
12541
12542    let language = Language::new(
12543        LanguageConfig {
12544            name: "Rust".into(),
12545            brackets: BracketPairConfig {
12546                pairs: vec![
12547                    BracketPair {
12548                        start: "{".to_string(),
12549                        end: "}".to_string(),
12550                        close: true,
12551                        surround: true,
12552                        newline: true,
12553                    },
12554                    BracketPair {
12555                        start: "(".to_string(),
12556                        end: ")".to_string(),
12557                        close: true,
12558                        surround: true,
12559                        newline: true,
12560                    },
12561                    BracketPair {
12562                        start: "/*".to_string(),
12563                        end: " */".to_string(),
12564                        close: true,
12565                        surround: true,
12566                        newline: true,
12567                    },
12568                    BracketPair {
12569                        start: "[".to_string(),
12570                        end: "]".to_string(),
12571                        close: false,
12572                        surround: false,
12573                        newline: true,
12574                    },
12575                    BracketPair {
12576                        start: "\"".to_string(),
12577                        end: "\"".to_string(),
12578                        close: true,
12579                        surround: true,
12580                        newline: false,
12581                    },
12582                    BracketPair {
12583                        start: "<".to_string(),
12584                        end: ">".to_string(),
12585                        close: false,
12586                        surround: true,
12587                        newline: true,
12588                    },
12589                ],
12590                ..Default::default()
12591            },
12592            autoclose_before: "})]".to_string(),
12593            ..Default::default()
12594        },
12595        Some(tree_sitter_rust::LANGUAGE.into()),
12596    );
12597    let language = Arc::new(language);
12598
12599    cx.language_registry().add(language.clone());
12600    cx.update_buffer(|buffer, cx| {
12601        buffer.set_language(Some(language), cx);
12602    });
12603
12604    cx.set_state(
12605        &r#"
12606            fn main() {
12607                sampleˇ
12608            }
12609        "#
12610        .unindent(),
12611    );
12612
12613    cx.update_editor(|editor, window, cx| {
12614        editor.handle_input("(", window, cx);
12615    });
12616    cx.assert_editor_state(
12617        &"
12618            fn main() {
12619                sample(ˇ)
12620            }
12621        "
12622        .unindent(),
12623    );
12624
12625    let mocked_response = lsp::SignatureHelp {
12626        signatures: vec![lsp::SignatureInformation {
12627            label: "fn sample(param1: u8, param2: u8)".to_string(),
12628            documentation: None,
12629            parameters: Some(vec![
12630                lsp::ParameterInformation {
12631                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12632                    documentation: None,
12633                },
12634                lsp::ParameterInformation {
12635                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12636                    documentation: None,
12637                },
12638            ]),
12639            active_parameter: None,
12640        }],
12641        active_signature: Some(0),
12642        active_parameter: Some(0),
12643    };
12644    handle_signature_help_request(&mut cx, mocked_response).await;
12645
12646    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12647        .await;
12648
12649    cx.editor(|editor, _, _| {
12650        let signature_help_state = editor.signature_help_state.popover().cloned();
12651        let signature = signature_help_state.unwrap();
12652        assert_eq!(
12653            signature.signatures[signature.current_signature].label,
12654            "fn sample(param1: u8, param2: u8)"
12655        );
12656    });
12657}
12658
12659#[gpui::test]
12660async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
12661    init_test(cx, |_| {});
12662
12663    cx.update(|cx| {
12664        cx.update_global::<SettingsStore, _>(|settings, cx| {
12665            settings.update_user_settings(cx, |settings| {
12666                settings.editor.auto_signature_help = Some(false);
12667                settings.editor.show_signature_help_after_edits = Some(false);
12668            });
12669        });
12670    });
12671
12672    let mut cx = EditorLspTestContext::new_rust(
12673        lsp::ServerCapabilities {
12674            signature_help_provider: Some(lsp::SignatureHelpOptions {
12675                ..Default::default()
12676            }),
12677            ..Default::default()
12678        },
12679        cx,
12680    )
12681    .await;
12682
12683    let language = Language::new(
12684        LanguageConfig {
12685            name: "Rust".into(),
12686            brackets: BracketPairConfig {
12687                pairs: vec![
12688                    BracketPair {
12689                        start: "{".to_string(),
12690                        end: "}".to_string(),
12691                        close: true,
12692                        surround: true,
12693                        newline: true,
12694                    },
12695                    BracketPair {
12696                        start: "(".to_string(),
12697                        end: ")".to_string(),
12698                        close: true,
12699                        surround: true,
12700                        newline: true,
12701                    },
12702                    BracketPair {
12703                        start: "/*".to_string(),
12704                        end: " */".to_string(),
12705                        close: true,
12706                        surround: true,
12707                        newline: true,
12708                    },
12709                    BracketPair {
12710                        start: "[".to_string(),
12711                        end: "]".to_string(),
12712                        close: false,
12713                        surround: false,
12714                        newline: true,
12715                    },
12716                    BracketPair {
12717                        start: "\"".to_string(),
12718                        end: "\"".to_string(),
12719                        close: true,
12720                        surround: true,
12721                        newline: false,
12722                    },
12723                    BracketPair {
12724                        start: "<".to_string(),
12725                        end: ">".to_string(),
12726                        close: false,
12727                        surround: true,
12728                        newline: true,
12729                    },
12730                ],
12731                ..Default::default()
12732            },
12733            autoclose_before: "})]".to_string(),
12734            ..Default::default()
12735        },
12736        Some(tree_sitter_rust::LANGUAGE.into()),
12737    );
12738    let language = Arc::new(language);
12739
12740    cx.language_registry().add(language.clone());
12741    cx.update_buffer(|buffer, cx| {
12742        buffer.set_language(Some(language), cx);
12743    });
12744
12745    // Ensure that signature_help is not called when no signature help is enabled.
12746    cx.set_state(
12747        &r#"
12748            fn main() {
12749                sampleˇ
12750            }
12751        "#
12752        .unindent(),
12753    );
12754    cx.update_editor(|editor, window, cx| {
12755        editor.handle_input("(", window, cx);
12756    });
12757    cx.assert_editor_state(
12758        &"
12759            fn main() {
12760                sample(ˇ)
12761            }
12762        "
12763        .unindent(),
12764    );
12765    cx.editor(|editor, _, _| {
12766        assert!(editor.signature_help_state.task().is_none());
12767    });
12768
12769    let mocked_response = lsp::SignatureHelp {
12770        signatures: vec![lsp::SignatureInformation {
12771            label: "fn sample(param1: u8, param2: u8)".to_string(),
12772            documentation: None,
12773            parameters: Some(vec![
12774                lsp::ParameterInformation {
12775                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12776                    documentation: None,
12777                },
12778                lsp::ParameterInformation {
12779                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12780                    documentation: None,
12781                },
12782            ]),
12783            active_parameter: None,
12784        }],
12785        active_signature: Some(0),
12786        active_parameter: Some(0),
12787    };
12788
12789    // Ensure that signature_help is called when enabled afte edits
12790    cx.update(|_, cx| {
12791        cx.update_global::<SettingsStore, _>(|settings, cx| {
12792            settings.update_user_settings(cx, |settings| {
12793                settings.editor.auto_signature_help = Some(false);
12794                settings.editor.show_signature_help_after_edits = Some(true);
12795            });
12796        });
12797    });
12798    cx.set_state(
12799        &r#"
12800            fn main() {
12801                sampleˇ
12802            }
12803        "#
12804        .unindent(),
12805    );
12806    cx.update_editor(|editor, window, cx| {
12807        editor.handle_input("(", window, cx);
12808    });
12809    cx.assert_editor_state(
12810        &"
12811            fn main() {
12812                sample(ˇ)
12813            }
12814        "
12815        .unindent(),
12816    );
12817    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12818    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12819        .await;
12820    cx.update_editor(|editor, _, _| {
12821        let signature_help_state = editor.signature_help_state.popover().cloned();
12822        assert!(signature_help_state.is_some());
12823        let signature = signature_help_state.unwrap();
12824        assert_eq!(
12825            signature.signatures[signature.current_signature].label,
12826            "fn sample(param1: u8, param2: u8)"
12827        );
12828        editor.signature_help_state = SignatureHelpState::default();
12829    });
12830
12831    // Ensure that signature_help is called when auto signature help override is enabled
12832    cx.update(|_, cx| {
12833        cx.update_global::<SettingsStore, _>(|settings, cx| {
12834            settings.update_user_settings(cx, |settings| {
12835                settings.editor.auto_signature_help = Some(true);
12836                settings.editor.show_signature_help_after_edits = Some(false);
12837            });
12838        });
12839    });
12840    cx.set_state(
12841        &r#"
12842            fn main() {
12843                sampleˇ
12844            }
12845        "#
12846        .unindent(),
12847    );
12848    cx.update_editor(|editor, window, cx| {
12849        editor.handle_input("(", window, cx);
12850    });
12851    cx.assert_editor_state(
12852        &"
12853            fn main() {
12854                sample(ˇ)
12855            }
12856        "
12857        .unindent(),
12858    );
12859    handle_signature_help_request(&mut cx, mocked_response).await;
12860    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12861        .await;
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
12873#[gpui::test]
12874async fn test_signature_help(cx: &mut TestAppContext) {
12875    init_test(cx, |_| {});
12876    cx.update(|cx| {
12877        cx.update_global::<SettingsStore, _>(|settings, cx| {
12878            settings.update_user_settings(cx, |settings| {
12879                settings.editor.auto_signature_help = Some(true);
12880            });
12881        });
12882    });
12883
12884    let mut cx = EditorLspTestContext::new_rust(
12885        lsp::ServerCapabilities {
12886            signature_help_provider: Some(lsp::SignatureHelpOptions {
12887                ..Default::default()
12888            }),
12889            ..Default::default()
12890        },
12891        cx,
12892    )
12893    .await;
12894
12895    // A test that directly calls `show_signature_help`
12896    cx.update_editor(|editor, window, cx| {
12897        editor.show_signature_help(&ShowSignatureHelp, window, cx);
12898    });
12899
12900    let mocked_response = lsp::SignatureHelp {
12901        signatures: vec![lsp::SignatureInformation {
12902            label: "fn sample(param1: u8, param2: u8)".to_string(),
12903            documentation: None,
12904            parameters: Some(vec![
12905                lsp::ParameterInformation {
12906                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12907                    documentation: None,
12908                },
12909                lsp::ParameterInformation {
12910                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12911                    documentation: None,
12912                },
12913            ]),
12914            active_parameter: None,
12915        }],
12916        active_signature: Some(0),
12917        active_parameter: Some(0),
12918    };
12919    handle_signature_help_request(&mut cx, mocked_response).await;
12920
12921    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12922        .await;
12923
12924    cx.editor(|editor, _, _| {
12925        let signature_help_state = editor.signature_help_state.popover().cloned();
12926        assert!(signature_help_state.is_some());
12927        let signature = signature_help_state.unwrap();
12928        assert_eq!(
12929            signature.signatures[signature.current_signature].label,
12930            "fn sample(param1: u8, param2: u8)"
12931        );
12932    });
12933
12934    // When exiting outside from inside the brackets, `signature_help` is closed.
12935    cx.set_state(indoc! {"
12936        fn main() {
12937            sample(ˇ);
12938        }
12939
12940        fn sample(param1: u8, param2: u8) {}
12941    "});
12942
12943    cx.update_editor(|editor, window, cx| {
12944        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12945            s.select_ranges([0..0])
12946        });
12947    });
12948
12949    let mocked_response = lsp::SignatureHelp {
12950        signatures: Vec::new(),
12951        active_signature: None,
12952        active_parameter: None,
12953    };
12954    handle_signature_help_request(&mut cx, mocked_response).await;
12955
12956    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
12957        .await;
12958
12959    cx.editor(|editor, _, _| {
12960        assert!(!editor.signature_help_state.is_shown());
12961    });
12962
12963    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
12964    cx.set_state(indoc! {"
12965        fn main() {
12966            sample(ˇ);
12967        }
12968
12969        fn sample(param1: u8, param2: u8) {}
12970    "});
12971
12972    let mocked_response = lsp::SignatureHelp {
12973        signatures: vec![lsp::SignatureInformation {
12974            label: "fn sample(param1: u8, param2: u8)".to_string(),
12975            documentation: None,
12976            parameters: Some(vec![
12977                lsp::ParameterInformation {
12978                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12979                    documentation: None,
12980                },
12981                lsp::ParameterInformation {
12982                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12983                    documentation: None,
12984                },
12985            ]),
12986            active_parameter: None,
12987        }],
12988        active_signature: Some(0),
12989        active_parameter: Some(0),
12990    };
12991    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12992    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12993        .await;
12994    cx.editor(|editor, _, _| {
12995        assert!(editor.signature_help_state.is_shown());
12996    });
12997
12998    // Restore the popover with more parameter input
12999    cx.set_state(indoc! {"
13000        fn main() {
13001            sample(param1, param2ˇ);
13002        }
13003
13004        fn sample(param1: u8, param2: u8) {}
13005    "});
13006
13007    let mocked_response = lsp::SignatureHelp {
13008        signatures: vec![lsp::SignatureInformation {
13009            label: "fn sample(param1: u8, param2: u8)".to_string(),
13010            documentation: None,
13011            parameters: Some(vec![
13012                lsp::ParameterInformation {
13013                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13014                    documentation: None,
13015                },
13016                lsp::ParameterInformation {
13017                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13018                    documentation: None,
13019                },
13020            ]),
13021            active_parameter: None,
13022        }],
13023        active_signature: Some(0),
13024        active_parameter: Some(1),
13025    };
13026    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13027    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13028        .await;
13029
13030    // When selecting a range, the popover is gone.
13031    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
13032    cx.update_editor(|editor, window, cx| {
13033        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13034            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13035        })
13036    });
13037    cx.assert_editor_state(indoc! {"
13038        fn main() {
13039            sample(param1, «ˇparam2»);
13040        }
13041
13042        fn sample(param1: u8, param2: u8) {}
13043    "});
13044    cx.editor(|editor, _, _| {
13045        assert!(!editor.signature_help_state.is_shown());
13046    });
13047
13048    // When unselecting again, the popover is back if within the brackets.
13049    cx.update_editor(|editor, window, cx| {
13050        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13051            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13052        })
13053    });
13054    cx.assert_editor_state(indoc! {"
13055        fn main() {
13056            sample(param1, ˇparam2);
13057        }
13058
13059        fn sample(param1: u8, param2: u8) {}
13060    "});
13061    handle_signature_help_request(&mut cx, mocked_response).await;
13062    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13063        .await;
13064    cx.editor(|editor, _, _| {
13065        assert!(editor.signature_help_state.is_shown());
13066    });
13067
13068    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
13069    cx.update_editor(|editor, window, cx| {
13070        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13071            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
13072            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13073        })
13074    });
13075    cx.assert_editor_state(indoc! {"
13076        fn main() {
13077            sample(param1, ˇparam2);
13078        }
13079
13080        fn sample(param1: u8, param2: u8) {}
13081    "});
13082
13083    let mocked_response = lsp::SignatureHelp {
13084        signatures: vec![lsp::SignatureInformation {
13085            label: "fn sample(param1: u8, param2: u8)".to_string(),
13086            documentation: None,
13087            parameters: Some(vec![
13088                lsp::ParameterInformation {
13089                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13090                    documentation: None,
13091                },
13092                lsp::ParameterInformation {
13093                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13094                    documentation: None,
13095                },
13096            ]),
13097            active_parameter: None,
13098        }],
13099        active_signature: Some(0),
13100        active_parameter: Some(1),
13101    };
13102    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13103    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13104        .await;
13105    cx.update_editor(|editor, _, cx| {
13106        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
13107    });
13108    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13109        .await;
13110    cx.update_editor(|editor, window, cx| {
13111        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13112            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13113        })
13114    });
13115    cx.assert_editor_state(indoc! {"
13116        fn main() {
13117            sample(param1, «ˇparam2»);
13118        }
13119
13120        fn sample(param1: u8, param2: u8) {}
13121    "});
13122    cx.update_editor(|editor, window, cx| {
13123        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13124            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13125        })
13126    });
13127    cx.assert_editor_state(indoc! {"
13128        fn main() {
13129            sample(param1, ˇparam2);
13130        }
13131
13132        fn sample(param1: u8, param2: u8) {}
13133    "});
13134    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
13135        .await;
13136}
13137
13138#[gpui::test]
13139async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
13140    init_test(cx, |_| {});
13141
13142    let mut cx = EditorLspTestContext::new_rust(
13143        lsp::ServerCapabilities {
13144            signature_help_provider: Some(lsp::SignatureHelpOptions {
13145                ..Default::default()
13146            }),
13147            ..Default::default()
13148        },
13149        cx,
13150    )
13151    .await;
13152
13153    cx.set_state(indoc! {"
13154        fn main() {
13155            overloadedˇ
13156        }
13157    "});
13158
13159    cx.update_editor(|editor, window, cx| {
13160        editor.handle_input("(", window, cx);
13161        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13162    });
13163
13164    // Mock response with 3 signatures
13165    let mocked_response = lsp::SignatureHelp {
13166        signatures: vec![
13167            lsp::SignatureInformation {
13168                label: "fn overloaded(x: i32)".to_string(),
13169                documentation: None,
13170                parameters: Some(vec![lsp::ParameterInformation {
13171                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13172                    documentation: None,
13173                }]),
13174                active_parameter: None,
13175            },
13176            lsp::SignatureInformation {
13177                label: "fn overloaded(x: i32, y: i32)".to_string(),
13178                documentation: None,
13179                parameters: Some(vec![
13180                    lsp::ParameterInformation {
13181                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13182                        documentation: None,
13183                    },
13184                    lsp::ParameterInformation {
13185                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13186                        documentation: None,
13187                    },
13188                ]),
13189                active_parameter: None,
13190            },
13191            lsp::SignatureInformation {
13192                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
13193                documentation: None,
13194                parameters: Some(vec![
13195                    lsp::ParameterInformation {
13196                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13197                        documentation: None,
13198                    },
13199                    lsp::ParameterInformation {
13200                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13201                        documentation: None,
13202                    },
13203                    lsp::ParameterInformation {
13204                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
13205                        documentation: None,
13206                    },
13207                ]),
13208                active_parameter: None,
13209            },
13210        ],
13211        active_signature: Some(1),
13212        active_parameter: Some(0),
13213    };
13214    handle_signature_help_request(&mut cx, mocked_response).await;
13215
13216    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13217        .await;
13218
13219    // Verify we have multiple signatures and the right one is selected
13220    cx.editor(|editor, _, _| {
13221        let popover = editor.signature_help_state.popover().cloned().unwrap();
13222        assert_eq!(popover.signatures.len(), 3);
13223        // active_signature was 1, so that should be the current
13224        assert_eq!(popover.current_signature, 1);
13225        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
13226        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
13227        assert_eq!(
13228            popover.signatures[2].label,
13229            "fn overloaded(x: i32, y: i32, z: i32)"
13230        );
13231    });
13232
13233    // Test navigation functionality
13234    cx.update_editor(|editor, window, cx| {
13235        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13236    });
13237
13238    cx.editor(|editor, _, _| {
13239        let popover = editor.signature_help_state.popover().cloned().unwrap();
13240        assert_eq!(popover.current_signature, 2);
13241    });
13242
13243    // Test wrap around
13244    cx.update_editor(|editor, window, cx| {
13245        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13246    });
13247
13248    cx.editor(|editor, _, _| {
13249        let popover = editor.signature_help_state.popover().cloned().unwrap();
13250        assert_eq!(popover.current_signature, 0);
13251    });
13252
13253    // Test previous navigation
13254    cx.update_editor(|editor, window, cx| {
13255        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
13256    });
13257
13258    cx.editor(|editor, _, _| {
13259        let popover = editor.signature_help_state.popover().cloned().unwrap();
13260        assert_eq!(popover.current_signature, 2);
13261    });
13262}
13263
13264#[gpui::test]
13265async fn test_completion_mode(cx: &mut TestAppContext) {
13266    init_test(cx, |_| {});
13267    let mut cx = EditorLspTestContext::new_rust(
13268        lsp::ServerCapabilities {
13269            completion_provider: Some(lsp::CompletionOptions {
13270                resolve_provider: Some(true),
13271                ..Default::default()
13272            }),
13273            ..Default::default()
13274        },
13275        cx,
13276    )
13277    .await;
13278
13279    struct Run {
13280        run_description: &'static str,
13281        initial_state: String,
13282        buffer_marked_text: String,
13283        completion_label: &'static str,
13284        completion_text: &'static str,
13285        expected_with_insert_mode: String,
13286        expected_with_replace_mode: String,
13287        expected_with_replace_subsequence_mode: String,
13288        expected_with_replace_suffix_mode: String,
13289    }
13290
13291    let runs = [
13292        Run {
13293            run_description: "Start of word matches completion text",
13294            initial_state: "before ediˇ after".into(),
13295            buffer_marked_text: "before <edi|> after".into(),
13296            completion_label: "editor",
13297            completion_text: "editor",
13298            expected_with_insert_mode: "before editorˇ after".into(),
13299            expected_with_replace_mode: "before editorˇ after".into(),
13300            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13301            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13302        },
13303        Run {
13304            run_description: "Accept same text at the middle of the word",
13305            initial_state: "before ediˇtor after".into(),
13306            buffer_marked_text: "before <edi|tor> after".into(),
13307            completion_label: "editor",
13308            completion_text: "editor",
13309            expected_with_insert_mode: "before editorˇtor after".into(),
13310            expected_with_replace_mode: "before editorˇ after".into(),
13311            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13312            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13313        },
13314        Run {
13315            run_description: "End of word matches completion text -- cursor at end",
13316            initial_state: "before torˇ after".into(),
13317            buffer_marked_text: "before <tor|> after".into(),
13318            completion_label: "editor",
13319            completion_text: "editor",
13320            expected_with_insert_mode: "before editorˇ after".into(),
13321            expected_with_replace_mode: "before editorˇ after".into(),
13322            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13323            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13324        },
13325        Run {
13326            run_description: "End of word matches completion text -- cursor at start",
13327            initial_state: "before ˇtor after".into(),
13328            buffer_marked_text: "before <|tor> after".into(),
13329            completion_label: "editor",
13330            completion_text: "editor",
13331            expected_with_insert_mode: "before editorˇtor after".into(),
13332            expected_with_replace_mode: "before editorˇ after".into(),
13333            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13334            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13335        },
13336        Run {
13337            run_description: "Prepend text containing whitespace",
13338            initial_state: "pˇfield: bool".into(),
13339            buffer_marked_text: "<p|field>: bool".into(),
13340            completion_label: "pub ",
13341            completion_text: "pub ",
13342            expected_with_insert_mode: "pub ˇfield: bool".into(),
13343            expected_with_replace_mode: "pub ˇ: bool".into(),
13344            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
13345            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
13346        },
13347        Run {
13348            run_description: "Add element to start of list",
13349            initial_state: "[element_ˇelement_2]".into(),
13350            buffer_marked_text: "[<element_|element_2>]".into(),
13351            completion_label: "element_1",
13352            completion_text: "element_1",
13353            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
13354            expected_with_replace_mode: "[element_1ˇ]".into(),
13355            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
13356            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
13357        },
13358        Run {
13359            run_description: "Add element to start of list -- first and second elements are equal",
13360            initial_state: "[elˇelement]".into(),
13361            buffer_marked_text: "[<el|element>]".into(),
13362            completion_label: "element",
13363            completion_text: "element",
13364            expected_with_insert_mode: "[elementˇelement]".into(),
13365            expected_with_replace_mode: "[elementˇ]".into(),
13366            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
13367            expected_with_replace_suffix_mode: "[elementˇ]".into(),
13368        },
13369        Run {
13370            run_description: "Ends with matching suffix",
13371            initial_state: "SubˇError".into(),
13372            buffer_marked_text: "<Sub|Error>".into(),
13373            completion_label: "SubscriptionError",
13374            completion_text: "SubscriptionError",
13375            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
13376            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13377            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13378            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
13379        },
13380        Run {
13381            run_description: "Suffix is a subsequence -- contiguous",
13382            initial_state: "SubˇErr".into(),
13383            buffer_marked_text: "<Sub|Err>".into(),
13384            completion_label: "SubscriptionError",
13385            completion_text: "SubscriptionError",
13386            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
13387            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13388            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13389            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
13390        },
13391        Run {
13392            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
13393            initial_state: "Suˇscrirr".into(),
13394            buffer_marked_text: "<Su|scrirr>".into(),
13395            completion_label: "SubscriptionError",
13396            completion_text: "SubscriptionError",
13397            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
13398            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13399            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13400            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
13401        },
13402        Run {
13403            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
13404            initial_state: "foo(indˇix)".into(),
13405            buffer_marked_text: "foo(<ind|ix>)".into(),
13406            completion_label: "node_index",
13407            completion_text: "node_index",
13408            expected_with_insert_mode: "foo(node_indexˇix)".into(),
13409            expected_with_replace_mode: "foo(node_indexˇ)".into(),
13410            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
13411            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
13412        },
13413        Run {
13414            run_description: "Replace range ends before cursor - should extend to cursor",
13415            initial_state: "before editˇo after".into(),
13416            buffer_marked_text: "before <{ed}>it|o after".into(),
13417            completion_label: "editor",
13418            completion_text: "editor",
13419            expected_with_insert_mode: "before editorˇo after".into(),
13420            expected_with_replace_mode: "before editorˇo after".into(),
13421            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
13422            expected_with_replace_suffix_mode: "before editorˇo after".into(),
13423        },
13424        Run {
13425            run_description: "Uses label for suffix matching",
13426            initial_state: "before ediˇtor after".into(),
13427            buffer_marked_text: "before <edi|tor> after".into(),
13428            completion_label: "editor",
13429            completion_text: "editor()",
13430            expected_with_insert_mode: "before editor()ˇtor after".into(),
13431            expected_with_replace_mode: "before editor()ˇ after".into(),
13432            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
13433            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
13434        },
13435        Run {
13436            run_description: "Case insensitive subsequence and suffix matching",
13437            initial_state: "before EDiˇtoR after".into(),
13438            buffer_marked_text: "before <EDi|toR> after".into(),
13439            completion_label: "editor",
13440            completion_text: "editor",
13441            expected_with_insert_mode: "before editorˇtoR after".into(),
13442            expected_with_replace_mode: "before editorˇ after".into(),
13443            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13444            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13445        },
13446    ];
13447
13448    for run in runs {
13449        let run_variations = [
13450            (LspInsertMode::Insert, run.expected_with_insert_mode),
13451            (LspInsertMode::Replace, run.expected_with_replace_mode),
13452            (
13453                LspInsertMode::ReplaceSubsequence,
13454                run.expected_with_replace_subsequence_mode,
13455            ),
13456            (
13457                LspInsertMode::ReplaceSuffix,
13458                run.expected_with_replace_suffix_mode,
13459            ),
13460        ];
13461
13462        for (lsp_insert_mode, expected_text) in run_variations {
13463            eprintln!(
13464                "run = {:?}, mode = {lsp_insert_mode:.?}",
13465                run.run_description,
13466            );
13467
13468            update_test_language_settings(&mut cx, |settings| {
13469                settings.defaults.completions = Some(CompletionSettingsContent {
13470                    lsp_insert_mode: Some(lsp_insert_mode),
13471                    words: Some(WordsCompletionMode::Disabled),
13472                    words_min_length: Some(0),
13473                    ..Default::default()
13474                });
13475            });
13476
13477            cx.set_state(&run.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                &run.buffer_marked_text,
13486                vec![(run.completion_label, run.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(&ConfirmCompletion::default(), window, cx)
13497                    .unwrap()
13498            });
13499            cx.assert_editor_state(&expected_text);
13500            handle_resolve_completion_request(&mut cx, None).await;
13501            apply_additional_edits.await.unwrap();
13502        }
13503    }
13504}
13505
13506#[gpui::test]
13507async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
13508    init_test(cx, |_| {});
13509    let mut cx = EditorLspTestContext::new_rust(
13510        lsp::ServerCapabilities {
13511            completion_provider: Some(lsp::CompletionOptions {
13512                resolve_provider: Some(true),
13513                ..Default::default()
13514            }),
13515            ..Default::default()
13516        },
13517        cx,
13518    )
13519    .await;
13520
13521    let initial_state = "SubˇError";
13522    let buffer_marked_text = "<Sub|Error>";
13523    let completion_text = "SubscriptionError";
13524    let expected_with_insert_mode = "SubscriptionErrorˇError";
13525    let expected_with_replace_mode = "SubscriptionErrorˇ";
13526
13527    update_test_language_settings(&mut cx, |settings| {
13528        settings.defaults.completions = Some(CompletionSettingsContent {
13529            words: Some(WordsCompletionMode::Disabled),
13530            words_min_length: Some(0),
13531            // set the opposite here to ensure that the action is overriding the default behavior
13532            lsp_insert_mode: Some(LspInsertMode::Insert),
13533            ..Default::default()
13534        });
13535    });
13536
13537    cx.set_state(initial_state);
13538    cx.update_editor(|editor, window, cx| {
13539        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13540    });
13541
13542    let counter = Arc::new(AtomicUsize::new(0));
13543    handle_completion_request_with_insert_and_replace(
13544        &mut cx,
13545        buffer_marked_text,
13546        vec![(completion_text, completion_text)],
13547        counter.clone(),
13548    )
13549    .await;
13550    cx.condition(|editor, _| editor.context_menu_visible())
13551        .await;
13552    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13553
13554    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13555        editor
13556            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13557            .unwrap()
13558    });
13559    cx.assert_editor_state(expected_with_replace_mode);
13560    handle_resolve_completion_request(&mut cx, None).await;
13561    apply_additional_edits.await.unwrap();
13562
13563    update_test_language_settings(&mut cx, |settings| {
13564        settings.defaults.completions = Some(CompletionSettingsContent {
13565            words: Some(WordsCompletionMode::Disabled),
13566            words_min_length: Some(0),
13567            // set the opposite here to ensure that the action is overriding the default behavior
13568            lsp_insert_mode: Some(LspInsertMode::Replace),
13569            ..Default::default()
13570        });
13571    });
13572
13573    cx.set_state(initial_state);
13574    cx.update_editor(|editor, window, cx| {
13575        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13576    });
13577    handle_completion_request_with_insert_and_replace(
13578        &mut cx,
13579        buffer_marked_text,
13580        vec![(completion_text, completion_text)],
13581        counter.clone(),
13582    )
13583    .await;
13584    cx.condition(|editor, _| editor.context_menu_visible())
13585        .await;
13586    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
13587
13588    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13589        editor
13590            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
13591            .unwrap()
13592    });
13593    cx.assert_editor_state(expected_with_insert_mode);
13594    handle_resolve_completion_request(&mut cx, None).await;
13595    apply_additional_edits.await.unwrap();
13596}
13597
13598#[gpui::test]
13599async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
13600    init_test(cx, |_| {});
13601    let mut cx = EditorLspTestContext::new_rust(
13602        lsp::ServerCapabilities {
13603            completion_provider: Some(lsp::CompletionOptions {
13604                resolve_provider: Some(true),
13605                ..Default::default()
13606            }),
13607            ..Default::default()
13608        },
13609        cx,
13610    )
13611    .await;
13612
13613    // scenario: surrounding text matches completion text
13614    let completion_text = "to_offset";
13615    let initial_state = indoc! {"
13616        1. buf.to_offˇsuffix
13617        2. buf.to_offˇsuf
13618        3. buf.to_offˇfix
13619        4. buf.to_offˇ
13620        5. into_offˇensive
13621        6. ˇsuffix
13622        7. let ˇ //
13623        8. aaˇzz
13624        9. buf.to_off«zzzzzˇ»suffix
13625        10. buf.«ˇzzzzz»suffix
13626        11. to_off«ˇzzzzz»
13627
13628        buf.to_offˇsuffix  // newest cursor
13629    "};
13630    let completion_marked_buffer = indoc! {"
13631        1. buf.to_offsuffix
13632        2. buf.to_offsuf
13633        3. buf.to_offfix
13634        4. buf.to_off
13635        5. into_offensive
13636        6. suffix
13637        7. let  //
13638        8. aazz
13639        9. buf.to_offzzzzzsuffix
13640        10. buf.zzzzzsuffix
13641        11. to_offzzzzz
13642
13643        buf.<to_off|suffix>  // newest cursor
13644    "};
13645    let expected = indoc! {"
13646        1. buf.to_offsetˇ
13647        2. buf.to_offsetˇsuf
13648        3. buf.to_offsetˇfix
13649        4. buf.to_offsetˇ
13650        5. into_offsetˇensive
13651        6. to_offsetˇsuffix
13652        7. let to_offsetˇ //
13653        8. aato_offsetˇzz
13654        9. buf.to_offsetˇ
13655        10. buf.to_offsetˇsuffix
13656        11. to_offsetˇ
13657
13658        buf.to_offsetˇ  // newest cursor
13659    "};
13660    cx.set_state(initial_state);
13661    cx.update_editor(|editor, window, cx| {
13662        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13663    });
13664    handle_completion_request_with_insert_and_replace(
13665        &mut cx,
13666        completion_marked_buffer,
13667        vec![(completion_text, completion_text)],
13668        Arc::new(AtomicUsize::new(0)),
13669    )
13670    .await;
13671    cx.condition(|editor, _| editor.context_menu_visible())
13672        .await;
13673    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13674        editor
13675            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13676            .unwrap()
13677    });
13678    cx.assert_editor_state(expected);
13679    handle_resolve_completion_request(&mut cx, None).await;
13680    apply_additional_edits.await.unwrap();
13681
13682    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
13683    let completion_text = "foo_and_bar";
13684    let initial_state = indoc! {"
13685        1. ooanbˇ
13686        2. zooanbˇ
13687        3. ooanbˇz
13688        4. zooanbˇz
13689        5. ooanˇ
13690        6. oanbˇ
13691
13692        ooanbˇ
13693    "};
13694    let completion_marked_buffer = indoc! {"
13695        1. ooanb
13696        2. zooanb
13697        3. ooanbz
13698        4. zooanbz
13699        5. ooan
13700        6. oanb
13701
13702        <ooanb|>
13703    "};
13704    let expected = indoc! {"
13705        1. foo_and_barˇ
13706        2. zfoo_and_barˇ
13707        3. foo_and_barˇz
13708        4. zfoo_and_barˇz
13709        5. ooanfoo_and_barˇ
13710        6. oanbfoo_and_barˇ
13711
13712        foo_and_barˇ
13713    "};
13714    cx.set_state(initial_state);
13715    cx.update_editor(|editor, window, cx| {
13716        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13717    });
13718    handle_completion_request_with_insert_and_replace(
13719        &mut cx,
13720        completion_marked_buffer,
13721        vec![(completion_text, completion_text)],
13722        Arc::new(AtomicUsize::new(0)),
13723    )
13724    .await;
13725    cx.condition(|editor, _| editor.context_menu_visible())
13726        .await;
13727    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13728        editor
13729            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13730            .unwrap()
13731    });
13732    cx.assert_editor_state(expected);
13733    handle_resolve_completion_request(&mut cx, None).await;
13734    apply_additional_edits.await.unwrap();
13735
13736    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
13737    // (expects the same as if it was inserted at the end)
13738    let completion_text = "foo_and_bar";
13739    let initial_state = indoc! {"
13740        1. ooˇanb
13741        2. zooˇanb
13742        3. ooˇanbz
13743        4. zooˇanbz
13744
13745        ooˇanb
13746    "};
13747    let completion_marked_buffer = indoc! {"
13748        1. ooanb
13749        2. zooanb
13750        3. ooanbz
13751        4. zooanbz
13752
13753        <oo|anb>
13754    "};
13755    let expected = indoc! {"
13756        1. foo_and_barˇ
13757        2. zfoo_and_barˇ
13758        3. foo_and_barˇz
13759        4. zfoo_and_barˇz
13760
13761        foo_and_barˇ
13762    "};
13763    cx.set_state(initial_state);
13764    cx.update_editor(|editor, window, cx| {
13765        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13766    });
13767    handle_completion_request_with_insert_and_replace(
13768        &mut cx,
13769        completion_marked_buffer,
13770        vec![(completion_text, completion_text)],
13771        Arc::new(AtomicUsize::new(0)),
13772    )
13773    .await;
13774    cx.condition(|editor, _| editor.context_menu_visible())
13775        .await;
13776    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13777        editor
13778            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13779            .unwrap()
13780    });
13781    cx.assert_editor_state(expected);
13782    handle_resolve_completion_request(&mut cx, None).await;
13783    apply_additional_edits.await.unwrap();
13784}
13785
13786// This used to crash
13787#[gpui::test]
13788async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
13789    init_test(cx, |_| {});
13790
13791    let buffer_text = indoc! {"
13792        fn main() {
13793            10.satu;
13794
13795            //
13796            // separate cursors so they open in different excerpts (manually reproducible)
13797            //
13798
13799            10.satu20;
13800        }
13801    "};
13802    let multibuffer_text_with_selections = indoc! {"
13803        fn main() {
13804            10.satuˇ;
13805
13806            //
13807
13808            //
13809
13810            10.satuˇ20;
13811        }
13812    "};
13813    let expected_multibuffer = indoc! {"
13814        fn main() {
13815            10.saturating_sub()ˇ;
13816
13817            //
13818
13819            //
13820
13821            10.saturating_sub()ˇ;
13822        }
13823    "};
13824
13825    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
13826    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
13827
13828    let fs = FakeFs::new(cx.executor());
13829    fs.insert_tree(
13830        path!("/a"),
13831        json!({
13832            "main.rs": buffer_text,
13833        }),
13834    )
13835    .await;
13836
13837    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13838    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13839    language_registry.add(rust_lang());
13840    let mut fake_servers = language_registry.register_fake_lsp(
13841        "Rust",
13842        FakeLspAdapter {
13843            capabilities: lsp::ServerCapabilities {
13844                completion_provider: Some(lsp::CompletionOptions {
13845                    resolve_provider: None,
13846                    ..lsp::CompletionOptions::default()
13847                }),
13848                ..lsp::ServerCapabilities::default()
13849            },
13850            ..FakeLspAdapter::default()
13851        },
13852    );
13853    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13854    let cx = &mut VisualTestContext::from_window(*workspace, cx);
13855    let buffer = project
13856        .update(cx, |project, cx| {
13857            project.open_local_buffer(path!("/a/main.rs"), cx)
13858        })
13859        .await
13860        .unwrap();
13861
13862    let multi_buffer = cx.new(|cx| {
13863        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
13864        multi_buffer.push_excerpts(
13865            buffer.clone(),
13866            [ExcerptRange::new(0..first_excerpt_end)],
13867            cx,
13868        );
13869        multi_buffer.push_excerpts(
13870            buffer.clone(),
13871            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
13872            cx,
13873        );
13874        multi_buffer
13875    });
13876
13877    let editor = workspace
13878        .update(cx, |_, window, cx| {
13879            cx.new(|cx| {
13880                Editor::new(
13881                    EditorMode::Full {
13882                        scale_ui_elements_with_buffer_font_size: false,
13883                        show_active_line_background: false,
13884                        sized_by_content: false,
13885                    },
13886                    multi_buffer.clone(),
13887                    Some(project.clone()),
13888                    window,
13889                    cx,
13890                )
13891            })
13892        })
13893        .unwrap();
13894
13895    let pane = workspace
13896        .update(cx, |workspace, _, _| workspace.active_pane().clone())
13897        .unwrap();
13898    pane.update_in(cx, |pane, window, cx| {
13899        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
13900    });
13901
13902    let fake_server = fake_servers.next().await.unwrap();
13903
13904    editor.update_in(cx, |editor, window, cx| {
13905        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13906            s.select_ranges([
13907                Point::new(1, 11)..Point::new(1, 11),
13908                Point::new(7, 11)..Point::new(7, 11),
13909            ])
13910        });
13911
13912        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
13913    });
13914
13915    editor.update_in(cx, |editor, window, cx| {
13916        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13917    });
13918
13919    fake_server
13920        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13921            let completion_item = lsp::CompletionItem {
13922                label: "saturating_sub()".into(),
13923                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
13924                    lsp::InsertReplaceEdit {
13925                        new_text: "saturating_sub()".to_owned(),
13926                        insert: lsp::Range::new(
13927                            lsp::Position::new(7, 7),
13928                            lsp::Position::new(7, 11),
13929                        ),
13930                        replace: lsp::Range::new(
13931                            lsp::Position::new(7, 7),
13932                            lsp::Position::new(7, 13),
13933                        ),
13934                    },
13935                )),
13936                ..lsp::CompletionItem::default()
13937            };
13938
13939            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
13940        })
13941        .next()
13942        .await
13943        .unwrap();
13944
13945    cx.condition(&editor, |editor, _| editor.context_menu_visible())
13946        .await;
13947
13948    editor
13949        .update_in(cx, |editor, window, cx| {
13950            editor
13951                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13952                .unwrap()
13953        })
13954        .await
13955        .unwrap();
13956
13957    editor.update(cx, |editor, cx| {
13958        assert_text_with_selections(editor, expected_multibuffer, cx);
13959    })
13960}
13961
13962#[gpui::test]
13963async fn test_completion(cx: &mut TestAppContext) {
13964    init_test(cx, |_| {});
13965
13966    let mut cx = EditorLspTestContext::new_rust(
13967        lsp::ServerCapabilities {
13968            completion_provider: Some(lsp::CompletionOptions {
13969                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
13970                resolve_provider: Some(true),
13971                ..Default::default()
13972            }),
13973            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13974            ..Default::default()
13975        },
13976        cx,
13977    )
13978    .await;
13979    let counter = Arc::new(AtomicUsize::new(0));
13980
13981    cx.set_state(indoc! {"
13982        oneˇ
13983        two
13984        three
13985    "});
13986    cx.simulate_keystroke(".");
13987    handle_completion_request(
13988        indoc! {"
13989            one.|<>
13990            two
13991            three
13992        "},
13993        vec!["first_completion", "second_completion"],
13994        true,
13995        counter.clone(),
13996        &mut cx,
13997    )
13998    .await;
13999    cx.condition(|editor, _| editor.context_menu_visible())
14000        .await;
14001    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14002
14003    let _handler = handle_signature_help_request(
14004        &mut cx,
14005        lsp::SignatureHelp {
14006            signatures: vec![lsp::SignatureInformation {
14007                label: "test signature".to_string(),
14008                documentation: None,
14009                parameters: Some(vec![lsp::ParameterInformation {
14010                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
14011                    documentation: None,
14012                }]),
14013                active_parameter: None,
14014            }],
14015            active_signature: None,
14016            active_parameter: None,
14017        },
14018    );
14019    cx.update_editor(|editor, window, cx| {
14020        assert!(
14021            !editor.signature_help_state.is_shown(),
14022            "No signature help was called for"
14023        );
14024        editor.show_signature_help(&ShowSignatureHelp, window, cx);
14025    });
14026    cx.run_until_parked();
14027    cx.update_editor(|editor, _, _| {
14028        assert!(
14029            !editor.signature_help_state.is_shown(),
14030            "No signature help should be shown when completions menu is open"
14031        );
14032    });
14033
14034    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14035        editor.context_menu_next(&Default::default(), window, cx);
14036        editor
14037            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14038            .unwrap()
14039    });
14040    cx.assert_editor_state(indoc! {"
14041        one.second_completionˇ
14042        two
14043        three
14044    "});
14045
14046    handle_resolve_completion_request(
14047        &mut cx,
14048        Some(vec![
14049            (
14050                //This overlaps with the primary completion edit which is
14051                //misbehavior from the LSP spec, test that we filter it out
14052                indoc! {"
14053                    one.second_ˇcompletion
14054                    two
14055                    threeˇ
14056                "},
14057                "overlapping additional edit",
14058            ),
14059            (
14060                indoc! {"
14061                    one.second_completion
14062                    two
14063                    threeˇ
14064                "},
14065                "\nadditional edit",
14066            ),
14067        ]),
14068    )
14069    .await;
14070    apply_additional_edits.await.unwrap();
14071    cx.assert_editor_state(indoc! {"
14072        one.second_completionˇ
14073        two
14074        three
14075        additional edit
14076    "});
14077
14078    cx.set_state(indoc! {"
14079        one.second_completion
14080        twoˇ
14081        threeˇ
14082        additional edit
14083    "});
14084    cx.simulate_keystroke(" ");
14085    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14086    cx.simulate_keystroke("s");
14087    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14088
14089    cx.assert_editor_state(indoc! {"
14090        one.second_completion
14091        two sˇ
14092        three sˇ
14093        additional edit
14094    "});
14095    handle_completion_request(
14096        indoc! {"
14097            one.second_completion
14098            two s
14099            three <s|>
14100            additional edit
14101        "},
14102        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14103        true,
14104        counter.clone(),
14105        &mut cx,
14106    )
14107    .await;
14108    cx.condition(|editor, _| editor.context_menu_visible())
14109        .await;
14110    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14111
14112    cx.simulate_keystroke("i");
14113
14114    handle_completion_request(
14115        indoc! {"
14116            one.second_completion
14117            two si
14118            three <si|>
14119            additional edit
14120        "},
14121        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14122        true,
14123        counter.clone(),
14124        &mut cx,
14125    )
14126    .await;
14127    cx.condition(|editor, _| editor.context_menu_visible())
14128        .await;
14129    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14130
14131    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14132        editor
14133            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14134            .unwrap()
14135    });
14136    cx.assert_editor_state(indoc! {"
14137        one.second_completion
14138        two sixth_completionˇ
14139        three sixth_completionˇ
14140        additional edit
14141    "});
14142
14143    apply_additional_edits.await.unwrap();
14144
14145    update_test_language_settings(&mut cx, |settings| {
14146        settings.defaults.show_completions_on_input = Some(false);
14147    });
14148    cx.set_state("editorˇ");
14149    cx.simulate_keystroke(".");
14150    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14151    cx.simulate_keystrokes("c l o");
14152    cx.assert_editor_state("editor.cloˇ");
14153    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14154    cx.update_editor(|editor, window, cx| {
14155        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14156    });
14157    handle_completion_request(
14158        "editor.<clo|>",
14159        vec!["close", "clobber"],
14160        true,
14161        counter.clone(),
14162        &mut cx,
14163    )
14164    .await;
14165    cx.condition(|editor, _| editor.context_menu_visible())
14166        .await;
14167    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
14168
14169    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14170        editor
14171            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14172            .unwrap()
14173    });
14174    cx.assert_editor_state("editor.clobberˇ");
14175    handle_resolve_completion_request(&mut cx, None).await;
14176    apply_additional_edits.await.unwrap();
14177}
14178
14179#[gpui::test]
14180async fn test_completion_reuse(cx: &mut TestAppContext) {
14181    init_test(cx, |_| {});
14182
14183    let mut cx = EditorLspTestContext::new_rust(
14184        lsp::ServerCapabilities {
14185            completion_provider: Some(lsp::CompletionOptions {
14186                trigger_characters: Some(vec![".".to_string()]),
14187                ..Default::default()
14188            }),
14189            ..Default::default()
14190        },
14191        cx,
14192    )
14193    .await;
14194
14195    let counter = Arc::new(AtomicUsize::new(0));
14196    cx.set_state("objˇ");
14197    cx.simulate_keystroke(".");
14198
14199    // Initial completion request returns complete results
14200    let is_incomplete = false;
14201    handle_completion_request(
14202        "obj.|<>",
14203        vec!["a", "ab", "abc"],
14204        is_incomplete,
14205        counter.clone(),
14206        &mut cx,
14207    )
14208    .await;
14209    cx.run_until_parked();
14210    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14211    cx.assert_editor_state("obj.ˇ");
14212    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14213
14214    // Type "a" - filters existing completions
14215    cx.simulate_keystroke("a");
14216    cx.run_until_parked();
14217    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14218    cx.assert_editor_state("obj.aˇ");
14219    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14220
14221    // Type "b" - filters existing completions
14222    cx.simulate_keystroke("b");
14223    cx.run_until_parked();
14224    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14225    cx.assert_editor_state("obj.abˇ");
14226    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14227
14228    // Type "c" - filters existing completions
14229    cx.simulate_keystroke("c");
14230    cx.run_until_parked();
14231    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14232    cx.assert_editor_state("obj.abcˇ");
14233    check_displayed_completions(vec!["abc"], &mut cx);
14234
14235    // Backspace to delete "c" - filters existing completions
14236    cx.update_editor(|editor, window, cx| {
14237        editor.backspace(&Backspace, window, cx);
14238    });
14239    cx.run_until_parked();
14240    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14241    cx.assert_editor_state("obj.abˇ");
14242    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14243
14244    // Moving cursor to the left dismisses menu.
14245    cx.update_editor(|editor, window, cx| {
14246        editor.move_left(&MoveLeft, window, cx);
14247    });
14248    cx.run_until_parked();
14249    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14250    cx.assert_editor_state("obj.aˇb");
14251    cx.update_editor(|editor, _, _| {
14252        assert_eq!(editor.context_menu_visible(), false);
14253    });
14254
14255    // Type "b" - new request
14256    cx.simulate_keystroke("b");
14257    let is_incomplete = false;
14258    handle_completion_request(
14259        "obj.<ab|>a",
14260        vec!["ab", "abc"],
14261        is_incomplete,
14262        counter.clone(),
14263        &mut cx,
14264    )
14265    .await;
14266    cx.run_until_parked();
14267    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14268    cx.assert_editor_state("obj.abˇb");
14269    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14270
14271    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
14272    cx.update_editor(|editor, window, cx| {
14273        editor.backspace(&Backspace, window, cx);
14274    });
14275    let is_incomplete = false;
14276    handle_completion_request(
14277        "obj.<a|>b",
14278        vec!["a", "ab", "abc"],
14279        is_incomplete,
14280        counter.clone(),
14281        &mut cx,
14282    )
14283    .await;
14284    cx.run_until_parked();
14285    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14286    cx.assert_editor_state("obj.aˇb");
14287    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14288
14289    // Backspace to delete "a" - dismisses menu.
14290    cx.update_editor(|editor, window, cx| {
14291        editor.backspace(&Backspace, window, cx);
14292    });
14293    cx.run_until_parked();
14294    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14295    cx.assert_editor_state("obj.ˇb");
14296    cx.update_editor(|editor, _, _| {
14297        assert_eq!(editor.context_menu_visible(), false);
14298    });
14299}
14300
14301#[gpui::test]
14302async fn test_word_completion(cx: &mut TestAppContext) {
14303    let lsp_fetch_timeout_ms = 10;
14304    init_test(cx, |language_settings| {
14305        language_settings.defaults.completions = Some(CompletionSettingsContent {
14306            words_min_length: Some(0),
14307            lsp_fetch_timeout_ms: Some(10),
14308            lsp_insert_mode: Some(LspInsertMode::Insert),
14309            ..Default::default()
14310        });
14311    });
14312
14313    let mut cx = EditorLspTestContext::new_rust(
14314        lsp::ServerCapabilities {
14315            completion_provider: Some(lsp::CompletionOptions {
14316                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14317                ..lsp::CompletionOptions::default()
14318            }),
14319            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14320            ..lsp::ServerCapabilities::default()
14321        },
14322        cx,
14323    )
14324    .await;
14325
14326    let throttle_completions = Arc::new(AtomicBool::new(false));
14327
14328    let lsp_throttle_completions = throttle_completions.clone();
14329    let _completion_requests_handler =
14330        cx.lsp
14331            .server
14332            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
14333                let lsp_throttle_completions = lsp_throttle_completions.clone();
14334                let cx = cx.clone();
14335                async move {
14336                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
14337                        cx.background_executor()
14338                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
14339                            .await;
14340                    }
14341                    Ok(Some(lsp::CompletionResponse::Array(vec![
14342                        lsp::CompletionItem {
14343                            label: "first".into(),
14344                            ..lsp::CompletionItem::default()
14345                        },
14346                        lsp::CompletionItem {
14347                            label: "last".into(),
14348                            ..lsp::CompletionItem::default()
14349                        },
14350                    ])))
14351                }
14352            });
14353
14354    cx.set_state(indoc! {"
14355        oneˇ
14356        two
14357        three
14358    "});
14359    cx.simulate_keystroke(".");
14360    cx.executor().run_until_parked();
14361    cx.condition(|editor, _| editor.context_menu_visible())
14362        .await;
14363    cx.update_editor(|editor, window, cx| {
14364        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14365        {
14366            assert_eq!(
14367                completion_menu_entries(menu),
14368                &["first", "last"],
14369                "When LSP server is fast to reply, no fallback word completions are used"
14370            );
14371        } else {
14372            panic!("expected completion menu to be open");
14373        }
14374        editor.cancel(&Cancel, window, cx);
14375    });
14376    cx.executor().run_until_parked();
14377    cx.condition(|editor, _| !editor.context_menu_visible())
14378        .await;
14379
14380    throttle_completions.store(true, atomic::Ordering::Release);
14381    cx.simulate_keystroke(".");
14382    cx.executor()
14383        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
14384    cx.executor().run_until_parked();
14385    cx.condition(|editor, _| editor.context_menu_visible())
14386        .await;
14387    cx.update_editor(|editor, _, _| {
14388        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14389        {
14390            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
14391                "When LSP server is slow, document words can be shown instead, if configured accordingly");
14392        } else {
14393            panic!("expected completion menu to be open");
14394        }
14395    });
14396}
14397
14398#[gpui::test]
14399async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
14400    init_test(cx, |language_settings| {
14401        language_settings.defaults.completions = Some(CompletionSettingsContent {
14402            words: Some(WordsCompletionMode::Enabled),
14403            words_min_length: Some(0),
14404            lsp_insert_mode: Some(LspInsertMode::Insert),
14405            ..Default::default()
14406        });
14407    });
14408
14409    let mut cx = EditorLspTestContext::new_rust(
14410        lsp::ServerCapabilities {
14411            completion_provider: Some(lsp::CompletionOptions {
14412                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14413                ..lsp::CompletionOptions::default()
14414            }),
14415            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14416            ..lsp::ServerCapabilities::default()
14417        },
14418        cx,
14419    )
14420    .await;
14421
14422    let _completion_requests_handler =
14423        cx.lsp
14424            .server
14425            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14426                Ok(Some(lsp::CompletionResponse::Array(vec![
14427                    lsp::CompletionItem {
14428                        label: "first".into(),
14429                        ..lsp::CompletionItem::default()
14430                    },
14431                    lsp::CompletionItem {
14432                        label: "last".into(),
14433                        ..lsp::CompletionItem::default()
14434                    },
14435                ])))
14436            });
14437
14438    cx.set_state(indoc! {"ˇ
14439        first
14440        last
14441        second
14442    "});
14443    cx.simulate_keystroke(".");
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                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
14454            );
14455        } else {
14456            panic!("expected completion menu to be open");
14457        }
14458    });
14459}
14460
14461#[gpui::test]
14462async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
14463    init_test(cx, |language_settings| {
14464        language_settings.defaults.completions = Some(CompletionSettingsContent {
14465            words: Some(WordsCompletionMode::Disabled),
14466            words_min_length: Some(0),
14467            lsp_insert_mode: Some(LspInsertMode::Insert),
14468            ..Default::default()
14469        });
14470    });
14471
14472    let mut cx = EditorLspTestContext::new_rust(
14473        lsp::ServerCapabilities {
14474            completion_provider: Some(lsp::CompletionOptions {
14475                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14476                ..lsp::CompletionOptions::default()
14477            }),
14478            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14479            ..lsp::ServerCapabilities::default()
14480        },
14481        cx,
14482    )
14483    .await;
14484
14485    let _completion_requests_handler =
14486        cx.lsp
14487            .server
14488            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14489                panic!("LSP completions should not be queried when dealing with word completions")
14490            });
14491
14492    cx.set_state(indoc! {"ˇ
14493        first
14494        last
14495        second
14496    "});
14497    cx.update_editor(|editor, window, cx| {
14498        editor.show_word_completions(&ShowWordCompletions, window, cx);
14499    });
14500    cx.executor().run_until_parked();
14501    cx.condition(|editor, _| editor.context_menu_visible())
14502        .await;
14503    cx.update_editor(|editor, _, _| {
14504        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14505        {
14506            assert_eq!(
14507                completion_menu_entries(menu),
14508                &["first", "last", "second"],
14509                "`ShowWordCompletions` action should show word completions"
14510            );
14511        } else {
14512            panic!("expected completion menu to be open");
14513        }
14514    });
14515
14516    cx.simulate_keystroke("l");
14517    cx.executor().run_until_parked();
14518    cx.condition(|editor, _| editor.context_menu_visible())
14519        .await;
14520    cx.update_editor(|editor, _, _| {
14521        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14522        {
14523            assert_eq!(
14524                completion_menu_entries(menu),
14525                &["last"],
14526                "After showing word completions, further editing should filter them and not query the LSP"
14527            );
14528        } else {
14529            panic!("expected completion menu to be open");
14530        }
14531    });
14532}
14533
14534#[gpui::test]
14535async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
14536    init_test(cx, |language_settings| {
14537        language_settings.defaults.completions = Some(CompletionSettingsContent {
14538            words_min_length: Some(0),
14539            lsp: Some(false),
14540            lsp_insert_mode: Some(LspInsertMode::Insert),
14541            ..Default::default()
14542        });
14543    });
14544
14545    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14546
14547    cx.set_state(indoc! {"ˇ
14548        0_usize
14549        let
14550        33
14551        4.5f32
14552    "});
14553    cx.update_editor(|editor, window, cx| {
14554        editor.show_completions(&ShowCompletions::default(), window, cx);
14555    });
14556    cx.executor().run_until_parked();
14557    cx.condition(|editor, _| editor.context_menu_visible())
14558        .await;
14559    cx.update_editor(|editor, window, cx| {
14560        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14561        {
14562            assert_eq!(
14563                completion_menu_entries(menu),
14564                &["let"],
14565                "With no digits in the completion query, no digits should be in the word completions"
14566            );
14567        } else {
14568            panic!("expected completion menu to be open");
14569        }
14570        editor.cancel(&Cancel, window, cx);
14571    });
14572
14573    cx.set_state(indoc! {"14574        0_usize
14575        let
14576        3
14577        33.35f32
14578    "});
14579    cx.update_editor(|editor, window, cx| {
14580        editor.show_completions(&ShowCompletions::default(), window, cx);
14581    });
14582    cx.executor().run_until_parked();
14583    cx.condition(|editor, _| editor.context_menu_visible())
14584        .await;
14585    cx.update_editor(|editor, _, _| {
14586        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14587        {
14588            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
14589                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
14590        } else {
14591            panic!("expected completion menu to be open");
14592        }
14593    });
14594}
14595
14596#[gpui::test]
14597async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
14598    init_test(cx, |language_settings| {
14599        language_settings.defaults.completions = Some(CompletionSettingsContent {
14600            words: Some(WordsCompletionMode::Enabled),
14601            words_min_length: Some(3),
14602            lsp_insert_mode: Some(LspInsertMode::Insert),
14603            ..Default::default()
14604        });
14605    });
14606
14607    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14608    cx.set_state(indoc! {"ˇ
14609        wow
14610        wowen
14611        wowser
14612    "});
14613    cx.simulate_keystroke("w");
14614    cx.executor().run_until_parked();
14615    cx.update_editor(|editor, _, _| {
14616        if editor.context_menu.borrow_mut().is_some() {
14617            panic!(
14618                "expected completion menu to be hidden, as words completion threshold is not met"
14619            );
14620        }
14621    });
14622
14623    cx.update_editor(|editor, window, cx| {
14624        editor.show_word_completions(&ShowWordCompletions, window, cx);
14625    });
14626    cx.executor().run_until_parked();
14627    cx.update_editor(|editor, window, cx| {
14628        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14629        {
14630            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");
14631        } else {
14632            panic!("expected completion menu to be open after the word completions are called with an action");
14633        }
14634
14635        editor.cancel(&Cancel, window, cx);
14636    });
14637    cx.update_editor(|editor, _, _| {
14638        if editor.context_menu.borrow_mut().is_some() {
14639            panic!("expected completion menu to be hidden after canceling");
14640        }
14641    });
14642
14643    cx.simulate_keystroke("o");
14644    cx.executor().run_until_parked();
14645    cx.update_editor(|editor, _, _| {
14646        if editor.context_menu.borrow_mut().is_some() {
14647            panic!(
14648                "expected completion menu to be hidden, as words completion threshold is not met still"
14649            );
14650        }
14651    });
14652
14653    cx.simulate_keystroke("w");
14654    cx.executor().run_until_parked();
14655    cx.update_editor(|editor, _, _| {
14656        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14657        {
14658            assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
14659        } else {
14660            panic!("expected completion menu to be open after the word completions threshold is met");
14661        }
14662    });
14663}
14664
14665#[gpui::test]
14666async fn test_word_completions_disabled(cx: &mut TestAppContext) {
14667    init_test(cx, |language_settings| {
14668        language_settings.defaults.completions = Some(CompletionSettingsContent {
14669            words: Some(WordsCompletionMode::Enabled),
14670            words_min_length: Some(0),
14671            lsp_insert_mode: Some(LspInsertMode::Insert),
14672            ..Default::default()
14673        });
14674    });
14675
14676    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14677    cx.update_editor(|editor, _, _| {
14678        editor.disable_word_completions();
14679    });
14680    cx.set_state(indoc! {"ˇ
14681        wow
14682        wowen
14683        wowser
14684    "});
14685    cx.simulate_keystroke("w");
14686    cx.executor().run_until_parked();
14687    cx.update_editor(|editor, _, _| {
14688        if editor.context_menu.borrow_mut().is_some() {
14689            panic!(
14690                "expected completion menu to be hidden, as words completion are disabled for this editor"
14691            );
14692        }
14693    });
14694
14695    cx.update_editor(|editor, window, cx| {
14696        editor.show_word_completions(&ShowWordCompletions, window, cx);
14697    });
14698    cx.executor().run_until_parked();
14699    cx.update_editor(|editor, _, _| {
14700        if editor.context_menu.borrow_mut().is_some() {
14701            panic!(
14702                "expected completion menu to be hidden even if called for explicitly, as words completion are disabled for this editor"
14703            );
14704        }
14705    });
14706}
14707
14708fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
14709    let position = || lsp::Position {
14710        line: params.text_document_position.position.line,
14711        character: params.text_document_position.position.character,
14712    };
14713    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14714        range: lsp::Range {
14715            start: position(),
14716            end: position(),
14717        },
14718        new_text: text.to_string(),
14719    }))
14720}
14721
14722#[gpui::test]
14723async fn test_multiline_completion(cx: &mut TestAppContext) {
14724    init_test(cx, |_| {});
14725
14726    let fs = FakeFs::new(cx.executor());
14727    fs.insert_tree(
14728        path!("/a"),
14729        json!({
14730            "main.ts": "a",
14731        }),
14732    )
14733    .await;
14734
14735    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14736    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14737    let typescript_language = Arc::new(Language::new(
14738        LanguageConfig {
14739            name: "TypeScript".into(),
14740            matcher: LanguageMatcher {
14741                path_suffixes: vec!["ts".to_string()],
14742                ..LanguageMatcher::default()
14743            },
14744            line_comments: vec!["// ".into()],
14745            ..LanguageConfig::default()
14746        },
14747        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
14748    ));
14749    language_registry.add(typescript_language.clone());
14750    let mut fake_servers = language_registry.register_fake_lsp(
14751        "TypeScript",
14752        FakeLspAdapter {
14753            capabilities: lsp::ServerCapabilities {
14754                completion_provider: Some(lsp::CompletionOptions {
14755                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14756                    ..lsp::CompletionOptions::default()
14757                }),
14758                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14759                ..lsp::ServerCapabilities::default()
14760            },
14761            // Emulate vtsls label generation
14762            label_for_completion: Some(Box::new(|item, _| {
14763                let text = if let Some(description) = item
14764                    .label_details
14765                    .as_ref()
14766                    .and_then(|label_details| label_details.description.as_ref())
14767                {
14768                    format!("{} {}", item.label, description)
14769                } else if let Some(detail) = &item.detail {
14770                    format!("{} {}", item.label, detail)
14771                } else {
14772                    item.label.clone()
14773                };
14774                let len = text.len();
14775                Some(language::CodeLabel {
14776                    text,
14777                    runs: Vec::new(),
14778                    filter_range: 0..len,
14779                })
14780            })),
14781            ..FakeLspAdapter::default()
14782        },
14783    );
14784    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14785    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14786    let worktree_id = workspace
14787        .update(cx, |workspace, _window, cx| {
14788            workspace.project().update(cx, |project, cx| {
14789                project.worktrees(cx).next().unwrap().read(cx).id()
14790            })
14791        })
14792        .unwrap();
14793    let _buffer = project
14794        .update(cx, |project, cx| {
14795            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
14796        })
14797        .await
14798        .unwrap();
14799    let editor = workspace
14800        .update(cx, |workspace, window, cx| {
14801            workspace.open_path((worktree_id, rel_path("main.ts")), None, true, window, cx)
14802        })
14803        .unwrap()
14804        .await
14805        .unwrap()
14806        .downcast::<Editor>()
14807        .unwrap();
14808    let fake_server = fake_servers.next().await.unwrap();
14809
14810    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
14811    let multiline_label_2 = "a\nb\nc\n";
14812    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
14813    let multiline_description = "d\ne\nf\n";
14814    let multiline_detail_2 = "g\nh\ni\n";
14815
14816    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
14817        move |params, _| async move {
14818            Ok(Some(lsp::CompletionResponse::Array(vec![
14819                lsp::CompletionItem {
14820                    label: multiline_label.to_string(),
14821                    text_edit: gen_text_edit(&params, "new_text_1"),
14822                    ..lsp::CompletionItem::default()
14823                },
14824                lsp::CompletionItem {
14825                    label: "single line label 1".to_string(),
14826                    detail: Some(multiline_detail.to_string()),
14827                    text_edit: gen_text_edit(&params, "new_text_2"),
14828                    ..lsp::CompletionItem::default()
14829                },
14830                lsp::CompletionItem {
14831                    label: "single line label 2".to_string(),
14832                    label_details: Some(lsp::CompletionItemLabelDetails {
14833                        description: Some(multiline_description.to_string()),
14834                        detail: None,
14835                    }),
14836                    text_edit: gen_text_edit(&params, "new_text_2"),
14837                    ..lsp::CompletionItem::default()
14838                },
14839                lsp::CompletionItem {
14840                    label: multiline_label_2.to_string(),
14841                    detail: Some(multiline_detail_2.to_string()),
14842                    text_edit: gen_text_edit(&params, "new_text_3"),
14843                    ..lsp::CompletionItem::default()
14844                },
14845                lsp::CompletionItem {
14846                    label: "Label with many     spaces and \t but without newlines".to_string(),
14847                    detail: Some(
14848                        "Details with many     spaces and \t but without newlines".to_string(),
14849                    ),
14850                    text_edit: gen_text_edit(&params, "new_text_4"),
14851                    ..lsp::CompletionItem::default()
14852                },
14853            ])))
14854        },
14855    );
14856
14857    editor.update_in(cx, |editor, window, cx| {
14858        cx.focus_self(window);
14859        editor.move_to_end(&MoveToEnd, window, cx);
14860        editor.handle_input(".", window, cx);
14861    });
14862    cx.run_until_parked();
14863    completion_handle.next().await.unwrap();
14864
14865    editor.update(cx, |editor, _| {
14866        assert!(editor.context_menu_visible());
14867        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14868        {
14869            let completion_labels = menu
14870                .completions
14871                .borrow()
14872                .iter()
14873                .map(|c| c.label.text.clone())
14874                .collect::<Vec<_>>();
14875            assert_eq!(
14876                completion_labels,
14877                &[
14878                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
14879                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
14880                    "single line label 2 d e f ",
14881                    "a b c g h i ",
14882                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
14883                ],
14884                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
14885            );
14886
14887            for completion in menu
14888                .completions
14889                .borrow()
14890                .iter() {
14891                    assert_eq!(
14892                        completion.label.filter_range,
14893                        0..completion.label.text.len(),
14894                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
14895                    );
14896                }
14897        } else {
14898            panic!("expected completion menu to be open");
14899        }
14900    });
14901}
14902
14903#[gpui::test]
14904async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
14905    init_test(cx, |_| {});
14906    let mut cx = EditorLspTestContext::new_rust(
14907        lsp::ServerCapabilities {
14908            completion_provider: Some(lsp::CompletionOptions {
14909                trigger_characters: Some(vec![".".to_string()]),
14910                ..Default::default()
14911            }),
14912            ..Default::default()
14913        },
14914        cx,
14915    )
14916    .await;
14917    cx.lsp
14918        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14919            Ok(Some(lsp::CompletionResponse::Array(vec![
14920                lsp::CompletionItem {
14921                    label: "first".into(),
14922                    ..Default::default()
14923                },
14924                lsp::CompletionItem {
14925                    label: "last".into(),
14926                    ..Default::default()
14927                },
14928            ])))
14929        });
14930    cx.set_state("variableˇ");
14931    cx.simulate_keystroke(".");
14932    cx.executor().run_until_parked();
14933
14934    cx.update_editor(|editor, _, _| {
14935        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14936        {
14937            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
14938        } else {
14939            panic!("expected completion menu to be open");
14940        }
14941    });
14942
14943    cx.update_editor(|editor, window, cx| {
14944        editor.move_page_down(&MovePageDown::default(), window, cx);
14945        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14946        {
14947            assert!(
14948                menu.selected_item == 1,
14949                "expected PageDown to select the last item from the context menu"
14950            );
14951        } else {
14952            panic!("expected completion menu to stay open after PageDown");
14953        }
14954    });
14955
14956    cx.update_editor(|editor, window, cx| {
14957        editor.move_page_up(&MovePageUp::default(), window, cx);
14958        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14959        {
14960            assert!(
14961                menu.selected_item == 0,
14962                "expected PageUp to select the first item from the context menu"
14963            );
14964        } else {
14965            panic!("expected completion menu to stay open after PageUp");
14966        }
14967    });
14968}
14969
14970#[gpui::test]
14971async fn test_as_is_completions(cx: &mut TestAppContext) {
14972    init_test(cx, |_| {});
14973    let mut cx = EditorLspTestContext::new_rust(
14974        lsp::ServerCapabilities {
14975            completion_provider: Some(lsp::CompletionOptions {
14976                ..Default::default()
14977            }),
14978            ..Default::default()
14979        },
14980        cx,
14981    )
14982    .await;
14983    cx.lsp
14984        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14985            Ok(Some(lsp::CompletionResponse::Array(vec![
14986                lsp::CompletionItem {
14987                    label: "unsafe".into(),
14988                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14989                        range: lsp::Range {
14990                            start: lsp::Position {
14991                                line: 1,
14992                                character: 2,
14993                            },
14994                            end: lsp::Position {
14995                                line: 1,
14996                                character: 3,
14997                            },
14998                        },
14999                        new_text: "unsafe".to_string(),
15000                    })),
15001                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
15002                    ..Default::default()
15003                },
15004            ])))
15005        });
15006    cx.set_state("fn a() {}\n");
15007    cx.executor().run_until_parked();
15008    cx.update_editor(|editor, window, cx| {
15009        editor.show_completions(
15010            &ShowCompletions {
15011                trigger: Some("\n".into()),
15012            },
15013            window,
15014            cx,
15015        );
15016    });
15017    cx.executor().run_until_parked();
15018
15019    cx.update_editor(|editor, window, cx| {
15020        editor.confirm_completion(&Default::default(), window, cx)
15021    });
15022    cx.executor().run_until_parked();
15023    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
15024}
15025
15026#[gpui::test]
15027async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
15028    init_test(cx, |_| {});
15029    let language =
15030        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
15031    let mut cx = EditorLspTestContext::new(
15032        language,
15033        lsp::ServerCapabilities {
15034            completion_provider: Some(lsp::CompletionOptions {
15035                ..lsp::CompletionOptions::default()
15036            }),
15037            ..lsp::ServerCapabilities::default()
15038        },
15039        cx,
15040    )
15041    .await;
15042
15043    cx.set_state(
15044        "#ifndef BAR_H
15045#define BAR_H
15046
15047#include <stdbool.h>
15048
15049int fn_branch(bool do_branch1, bool do_branch2);
15050
15051#endif // BAR_H
15052ˇ",
15053    );
15054    cx.executor().run_until_parked();
15055    cx.update_editor(|editor, window, cx| {
15056        editor.handle_input("#", window, cx);
15057    });
15058    cx.executor().run_until_parked();
15059    cx.update_editor(|editor, window, cx| {
15060        editor.handle_input("i", window, cx);
15061    });
15062    cx.executor().run_until_parked();
15063    cx.update_editor(|editor, window, cx| {
15064        editor.handle_input("n", 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#inˇ",
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: false,
15083                item_defaults: None,
15084                items: vec![lsp::CompletionItem {
15085                    kind: Some(lsp::CompletionItemKind::SNIPPET),
15086                    label_details: Some(lsp::CompletionItemLabelDetails {
15087                        detail: Some("header".to_string()),
15088                        description: None,
15089                    }),
15090                    label: " include".to_string(),
15091                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15092                        range: lsp::Range {
15093                            start: lsp::Position {
15094                                line: 8,
15095                                character: 1,
15096                            },
15097                            end: lsp::Position {
15098                                line: 8,
15099                                character: 1,
15100                            },
15101                        },
15102                        new_text: "include \"$0\"".to_string(),
15103                    })),
15104                    sort_text: Some("40b67681include".to_string()),
15105                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15106                    filter_text: Some("include".to_string()),
15107                    insert_text: Some("include \"$0\"".to_string()),
15108                    ..lsp::CompletionItem::default()
15109                }],
15110            })))
15111        });
15112    cx.update_editor(|editor, window, cx| {
15113        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15114    });
15115    cx.executor().run_until_parked();
15116    cx.update_editor(|editor, window, cx| {
15117        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15118    });
15119    cx.executor().run_until_parked();
15120    cx.assert_editor_state(
15121        "#ifndef BAR_H
15122#define BAR_H
15123
15124#include <stdbool.h>
15125
15126int fn_branch(bool do_branch1, bool do_branch2);
15127
15128#endif // BAR_H
15129#include \"ˇ\"",
15130    );
15131
15132    cx.lsp
15133        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15134            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15135                is_incomplete: true,
15136                item_defaults: None,
15137                items: vec![lsp::CompletionItem {
15138                    kind: Some(lsp::CompletionItemKind::FILE),
15139                    label: "AGL/".to_string(),
15140                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15141                        range: lsp::Range {
15142                            start: lsp::Position {
15143                                line: 8,
15144                                character: 10,
15145                            },
15146                            end: lsp::Position {
15147                                line: 8,
15148                                character: 11,
15149                            },
15150                        },
15151                        new_text: "AGL/".to_string(),
15152                    })),
15153                    sort_text: Some("40b67681AGL/".to_string()),
15154                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
15155                    filter_text: Some("AGL/".to_string()),
15156                    insert_text: Some("AGL/".to_string()),
15157                    ..lsp::CompletionItem::default()
15158                }],
15159            })))
15160        });
15161    cx.update_editor(|editor, window, cx| {
15162        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15163    });
15164    cx.executor().run_until_parked();
15165    cx.update_editor(|editor, window, cx| {
15166        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15167    });
15168    cx.executor().run_until_parked();
15169    cx.assert_editor_state(
15170        r##"#ifndef BAR_H
15171#define BAR_H
15172
15173#include <stdbool.h>
15174
15175int fn_branch(bool do_branch1, bool do_branch2);
15176
15177#endif // BAR_H
15178#include "AGL/ˇ"##,
15179    );
15180
15181    cx.update_editor(|editor, window, cx| {
15182        editor.handle_input("\"", window, cx);
15183    });
15184    cx.executor().run_until_parked();
15185    cx.assert_editor_state(
15186        r##"#ifndef BAR_H
15187#define BAR_H
15188
15189#include <stdbool.h>
15190
15191int fn_branch(bool do_branch1, bool do_branch2);
15192
15193#endif // BAR_H
15194#include "AGL/"ˇ"##,
15195    );
15196}
15197
15198#[gpui::test]
15199async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
15200    init_test(cx, |_| {});
15201
15202    let mut cx = EditorLspTestContext::new_rust(
15203        lsp::ServerCapabilities {
15204            completion_provider: Some(lsp::CompletionOptions {
15205                trigger_characters: Some(vec![".".to_string()]),
15206                resolve_provider: Some(true),
15207                ..Default::default()
15208            }),
15209            ..Default::default()
15210        },
15211        cx,
15212    )
15213    .await;
15214
15215    cx.set_state("fn main() { let a = 2ˇ; }");
15216    cx.simulate_keystroke(".");
15217    let completion_item = lsp::CompletionItem {
15218        label: "Some".into(),
15219        kind: Some(lsp::CompletionItemKind::SNIPPET),
15220        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15221        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15222            kind: lsp::MarkupKind::Markdown,
15223            value: "```rust\nSome(2)\n```".to_string(),
15224        })),
15225        deprecated: Some(false),
15226        sort_text: Some("Some".to_string()),
15227        filter_text: Some("Some".to_string()),
15228        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15229        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15230            range: lsp::Range {
15231                start: lsp::Position {
15232                    line: 0,
15233                    character: 22,
15234                },
15235                end: lsp::Position {
15236                    line: 0,
15237                    character: 22,
15238                },
15239            },
15240            new_text: "Some(2)".to_string(),
15241        })),
15242        additional_text_edits: Some(vec![lsp::TextEdit {
15243            range: lsp::Range {
15244                start: lsp::Position {
15245                    line: 0,
15246                    character: 20,
15247                },
15248                end: lsp::Position {
15249                    line: 0,
15250                    character: 22,
15251                },
15252            },
15253            new_text: "".to_string(),
15254        }]),
15255        ..Default::default()
15256    };
15257
15258    let closure_completion_item = completion_item.clone();
15259    let counter = Arc::new(AtomicUsize::new(0));
15260    let counter_clone = counter.clone();
15261    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15262        let task_completion_item = closure_completion_item.clone();
15263        counter_clone.fetch_add(1, atomic::Ordering::Release);
15264        async move {
15265            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15266                is_incomplete: true,
15267                item_defaults: None,
15268                items: vec![task_completion_item],
15269            })))
15270        }
15271    });
15272
15273    cx.condition(|editor, _| editor.context_menu_visible())
15274        .await;
15275    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
15276    assert!(request.next().await.is_some());
15277    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15278
15279    cx.simulate_keystrokes("S o m");
15280    cx.condition(|editor, _| editor.context_menu_visible())
15281        .await;
15282    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
15283    assert!(request.next().await.is_some());
15284    assert!(request.next().await.is_some());
15285    assert!(request.next().await.is_some());
15286    request.close();
15287    assert!(request.next().await.is_none());
15288    assert_eq!(
15289        counter.load(atomic::Ordering::Acquire),
15290        4,
15291        "With the completions menu open, only one LSP request should happen per input"
15292    );
15293}
15294
15295#[gpui::test]
15296async fn test_toggle_comment(cx: &mut TestAppContext) {
15297    init_test(cx, |_| {});
15298    let mut cx = EditorTestContext::new(cx).await;
15299    let language = Arc::new(Language::new(
15300        LanguageConfig {
15301            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15302            ..Default::default()
15303        },
15304        Some(tree_sitter_rust::LANGUAGE.into()),
15305    ));
15306    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15307
15308    // If multiple selections intersect a line, the line is only toggled once.
15309    cx.set_state(indoc! {"
15310        fn a() {
15311            «//b();
15312            ˇ»// «c();
15313            //ˇ»  d();
15314        }
15315    "});
15316
15317    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15318
15319    cx.assert_editor_state(indoc! {"
15320        fn a() {
15321            «b();
15322            c();
15323            ˇ» d();
15324        }
15325    "});
15326
15327    // The comment prefix is inserted at the same column for every line in a
15328    // selection.
15329    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15330
15331    cx.assert_editor_state(indoc! {"
15332        fn a() {
15333            // «b();
15334            // c();
15335            ˇ»//  d();
15336        }
15337    "});
15338
15339    // If a selection ends at the beginning of a line, that line is not toggled.
15340    cx.set_selections_state(indoc! {"
15341        fn a() {
15342            // b();
15343            «// c();
15344        ˇ»    //  d();
15345        }
15346    "});
15347
15348    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15349
15350    cx.assert_editor_state(indoc! {"
15351        fn a() {
15352            // b();
15353            «c();
15354        ˇ»    //  d();
15355        }
15356    "});
15357
15358    // If a selection span a single line and is empty, the line is toggled.
15359    cx.set_state(indoc! {"
15360        fn a() {
15361            a();
15362            b();
15363        ˇ
15364        }
15365    "});
15366
15367    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15368
15369    cx.assert_editor_state(indoc! {"
15370        fn a() {
15371            a();
15372            b();
15373        //•ˇ
15374        }
15375    "});
15376
15377    // If a selection span multiple lines, empty lines are not toggled.
15378    cx.set_state(indoc! {"
15379        fn a() {
15380            «a();
15381
15382            c();ˇ»
15383        }
15384    "});
15385
15386    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15387
15388    cx.assert_editor_state(indoc! {"
15389        fn a() {
15390            // «a();
15391
15392            // c();ˇ»
15393        }
15394    "});
15395
15396    // If a selection includes multiple comment prefixes, all lines are uncommented.
15397    cx.set_state(indoc! {"
15398        fn a() {
15399            «// a();
15400            /// b();
15401            //! c();ˇ»
15402        }
15403    "});
15404
15405    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15406
15407    cx.assert_editor_state(indoc! {"
15408        fn a() {
15409            «a();
15410            b();
15411            c();ˇ»
15412        }
15413    "});
15414}
15415
15416#[gpui::test]
15417async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
15418    init_test(cx, |_| {});
15419    let mut cx = EditorTestContext::new(cx).await;
15420    let language = Arc::new(Language::new(
15421        LanguageConfig {
15422            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15423            ..Default::default()
15424        },
15425        Some(tree_sitter_rust::LANGUAGE.into()),
15426    ));
15427    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15428
15429    let toggle_comments = &ToggleComments {
15430        advance_downwards: false,
15431        ignore_indent: true,
15432    };
15433
15434    // If multiple selections intersect a line, the line is only toggled once.
15435    cx.set_state(indoc! {"
15436        fn a() {
15437        //    «b();
15438        //    c();
15439        //    ˇ» d();
15440        }
15441    "});
15442
15443    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15444
15445    cx.assert_editor_state(indoc! {"
15446        fn a() {
15447            «b();
15448            c();
15449            ˇ» d();
15450        }
15451    "});
15452
15453    // The comment prefix is inserted at the beginning of each line
15454    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15455
15456    cx.assert_editor_state(indoc! {"
15457        fn a() {
15458        //    «b();
15459        //    c();
15460        //    ˇ» d();
15461        }
15462    "});
15463
15464    // If a selection ends at the beginning of a line, that line is not toggled.
15465    cx.set_selections_state(indoc! {"
15466        fn a() {
15467        //    b();
15468        //    «c();
15469        ˇ»//     d();
15470        }
15471    "});
15472
15473    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15474
15475    cx.assert_editor_state(indoc! {"
15476        fn a() {
15477        //    b();
15478            «c();
15479        ˇ»//     d();
15480        }
15481    "});
15482
15483    // If a selection span a single line and is empty, the line is toggled.
15484    cx.set_state(indoc! {"
15485        fn a() {
15486            a();
15487            b();
15488        ˇ
15489        }
15490    "});
15491
15492    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15493
15494    cx.assert_editor_state(indoc! {"
15495        fn a() {
15496            a();
15497            b();
15498        //ˇ
15499        }
15500    "});
15501
15502    // If a selection span multiple lines, empty lines are not toggled.
15503    cx.set_state(indoc! {"
15504        fn a() {
15505            «a();
15506
15507            c();ˇ»
15508        }
15509    "});
15510
15511    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15512
15513    cx.assert_editor_state(indoc! {"
15514        fn a() {
15515        //    «a();
15516
15517        //    c();ˇ»
15518        }
15519    "});
15520
15521    // If a selection includes multiple comment prefixes, all lines are uncommented.
15522    cx.set_state(indoc! {"
15523        fn a() {
15524        //    «a();
15525        ///    b();
15526        //!    c();ˇ»
15527        }
15528    "});
15529
15530    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15531
15532    cx.assert_editor_state(indoc! {"
15533        fn a() {
15534            «a();
15535            b();
15536            c();ˇ»
15537        }
15538    "});
15539}
15540
15541#[gpui::test]
15542async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
15543    init_test(cx, |_| {});
15544
15545    let language = Arc::new(Language::new(
15546        LanguageConfig {
15547            line_comments: vec!["// ".into()],
15548            ..Default::default()
15549        },
15550        Some(tree_sitter_rust::LANGUAGE.into()),
15551    ));
15552
15553    let mut cx = EditorTestContext::new(cx).await;
15554
15555    cx.language_registry().add(language.clone());
15556    cx.update_buffer(|buffer, cx| {
15557        buffer.set_language(Some(language), cx);
15558    });
15559
15560    let toggle_comments = &ToggleComments {
15561        advance_downwards: true,
15562        ignore_indent: false,
15563    };
15564
15565    // Single cursor on one line -> advance
15566    // Cursor moves horizontally 3 characters as well on non-blank line
15567    cx.set_state(indoc!(
15568        "fn a() {
15569             ˇdog();
15570             cat();
15571        }"
15572    ));
15573    cx.update_editor(|editor, window, cx| {
15574        editor.toggle_comments(toggle_comments, window, cx);
15575    });
15576    cx.assert_editor_state(indoc!(
15577        "fn a() {
15578             // dog();
15579             catˇ();
15580        }"
15581    ));
15582
15583    // Single selection on one line -> don't advance
15584    cx.set_state(indoc!(
15585        "fn a() {
15586             «dog()ˇ»;
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             cat();
15597        }"
15598    ));
15599
15600    // Multiple cursors on one line -> advance
15601    cx.set_state(indoc!(
15602        "fn a() {
15603             ˇdˇog();
15604             cat();
15605        }"
15606    ));
15607    cx.update_editor(|editor, window, cx| {
15608        editor.toggle_comments(toggle_comments, window, cx);
15609    });
15610    cx.assert_editor_state(indoc!(
15611        "fn a() {
15612             // dog();
15613             catˇ(ˇ);
15614        }"
15615    ));
15616
15617    // Multiple cursors on one line, with selection -> don't advance
15618    cx.set_state(indoc!(
15619        "fn a() {
15620             ˇdˇog«()ˇ»;
15621             cat();
15622        }"
15623    ));
15624    cx.update_editor(|editor, window, cx| {
15625        editor.toggle_comments(toggle_comments, window, cx);
15626    });
15627    cx.assert_editor_state(indoc!(
15628        "fn a() {
15629             // ˇdˇog«()ˇ»;
15630             cat();
15631        }"
15632    ));
15633
15634    // Single cursor on one line -> advance
15635    // Cursor moves to column 0 on blank line
15636    cx.set_state(indoc!(
15637        "fn a() {
15638             ˇdog();
15639
15640             cat();
15641        }"
15642    ));
15643    cx.update_editor(|editor, window, cx| {
15644        editor.toggle_comments(toggle_comments, window, cx);
15645    });
15646    cx.assert_editor_state(indoc!(
15647        "fn a() {
15648             // dog();
15649        ˇ
15650             cat();
15651        }"
15652    ));
15653
15654    // Single cursor on one line -> advance
15655    // Cursor starts and ends at column 0
15656    cx.set_state(indoc!(
15657        "fn a() {
15658         ˇ    dog();
15659             cat();
15660        }"
15661    ));
15662    cx.update_editor(|editor, window, cx| {
15663        editor.toggle_comments(toggle_comments, window, cx);
15664    });
15665    cx.assert_editor_state(indoc!(
15666        "fn a() {
15667             // dog();
15668         ˇ    cat();
15669        }"
15670    ));
15671}
15672
15673#[gpui::test]
15674async fn test_toggle_block_comment(cx: &mut TestAppContext) {
15675    init_test(cx, |_| {});
15676
15677    let mut cx = EditorTestContext::new(cx).await;
15678
15679    let html_language = Arc::new(
15680        Language::new(
15681            LanguageConfig {
15682                name: "HTML".into(),
15683                block_comment: Some(BlockCommentConfig {
15684                    start: "<!-- ".into(),
15685                    prefix: "".into(),
15686                    end: " -->".into(),
15687                    tab_size: 0,
15688                }),
15689                ..Default::default()
15690            },
15691            Some(tree_sitter_html::LANGUAGE.into()),
15692        )
15693        .with_injection_query(
15694            r#"
15695            (script_element
15696                (raw_text) @injection.content
15697                (#set! injection.language "javascript"))
15698            "#,
15699        )
15700        .unwrap(),
15701    );
15702
15703    let javascript_language = Arc::new(Language::new(
15704        LanguageConfig {
15705            name: "JavaScript".into(),
15706            line_comments: vec!["// ".into()],
15707            ..Default::default()
15708        },
15709        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
15710    ));
15711
15712    cx.language_registry().add(html_language.clone());
15713    cx.language_registry().add(javascript_language);
15714    cx.update_buffer(|buffer, cx| {
15715        buffer.set_language(Some(html_language), cx);
15716    });
15717
15718    // Toggle comments for empty selections
15719    cx.set_state(
15720        &r#"
15721            <p>A</p>ˇ
15722            <p>B</p>ˇ
15723            <p>C</p>ˇ
15724        "#
15725        .unindent(),
15726    );
15727    cx.update_editor(|editor, window, cx| {
15728        editor.toggle_comments(&ToggleComments::default(), window, cx)
15729    });
15730    cx.assert_editor_state(
15731        &r#"
15732            <!-- <p>A</p>ˇ -->
15733            <!-- <p>B</p>ˇ -->
15734            <!-- <p>C</p>ˇ -->
15735        "#
15736        .unindent(),
15737    );
15738    cx.update_editor(|editor, window, cx| {
15739        editor.toggle_comments(&ToggleComments::default(), window, cx)
15740    });
15741    cx.assert_editor_state(
15742        &r#"
15743            <p>A</p>ˇ
15744            <p>B</p>ˇ
15745            <p>C</p>ˇ
15746        "#
15747        .unindent(),
15748    );
15749
15750    // Toggle comments for mixture of empty and non-empty selections, where
15751    // multiple selections occupy a given line.
15752    cx.set_state(
15753        &r#"
15754            <p>A«</p>
15755            <p>ˇ»B</p>ˇ
15756            <p>C«</p>
15757            <p>ˇ»D</p>ˇ
15758        "#
15759        .unindent(),
15760    );
15761
15762    cx.update_editor(|editor, window, cx| {
15763        editor.toggle_comments(&ToggleComments::default(), window, cx)
15764    });
15765    cx.assert_editor_state(
15766        &r#"
15767            <!-- <p>A«</p>
15768            <p>ˇ»B</p>ˇ -->
15769            <!-- <p>C«</p>
15770            <p>ˇ»D</p>ˇ -->
15771        "#
15772        .unindent(),
15773    );
15774    cx.update_editor(|editor, window, cx| {
15775        editor.toggle_comments(&ToggleComments::default(), window, cx)
15776    });
15777    cx.assert_editor_state(
15778        &r#"
15779            <p>A«</p>
15780            <p>ˇ»B</p>ˇ
15781            <p>C«</p>
15782            <p>ˇ»D</p>ˇ
15783        "#
15784        .unindent(),
15785    );
15786
15787    // Toggle comments when different languages are active for different
15788    // selections.
15789    cx.set_state(
15790        &r#"
15791            ˇ<script>
15792                ˇvar x = new Y();
15793            ˇ</script>
15794        "#
15795        .unindent(),
15796    );
15797    cx.executor().run_until_parked();
15798    cx.update_editor(|editor, window, cx| {
15799        editor.toggle_comments(&ToggleComments::default(), window, cx)
15800    });
15801    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
15802    // Uncommenting and commenting from this position brings in even more wrong artifacts.
15803    cx.assert_editor_state(
15804        &r#"
15805            <!-- ˇ<script> -->
15806                // ˇvar x = new Y();
15807            <!-- ˇ</script> -->
15808        "#
15809        .unindent(),
15810    );
15811}
15812
15813#[gpui::test]
15814fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
15815    init_test(cx, |_| {});
15816
15817    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15818    let multibuffer = cx.new(|cx| {
15819        let mut multibuffer = MultiBuffer::new(ReadWrite);
15820        multibuffer.push_excerpts(
15821            buffer.clone(),
15822            [
15823                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
15824                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
15825            ],
15826            cx,
15827        );
15828        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
15829        multibuffer
15830    });
15831
15832    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
15833    editor.update_in(cx, |editor, window, cx| {
15834        assert_eq!(editor.text(cx), "aaaa\nbbbb");
15835        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15836            s.select_ranges([
15837                Point::new(0, 0)..Point::new(0, 0),
15838                Point::new(1, 0)..Point::new(1, 0),
15839            ])
15840        });
15841
15842        editor.handle_input("X", window, cx);
15843        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
15844        assert_eq!(
15845            editor.selections.ranges(cx),
15846            [
15847                Point::new(0, 1)..Point::new(0, 1),
15848                Point::new(1, 1)..Point::new(1, 1),
15849            ]
15850        );
15851
15852        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
15853        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15854            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
15855        });
15856        editor.backspace(&Default::default(), window, cx);
15857        assert_eq!(editor.text(cx), "Xa\nbbb");
15858        assert_eq!(
15859            editor.selections.ranges(cx),
15860            [Point::new(1, 0)..Point::new(1, 0)]
15861        );
15862
15863        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15864            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
15865        });
15866        editor.backspace(&Default::default(), window, cx);
15867        assert_eq!(editor.text(cx), "X\nbb");
15868        assert_eq!(
15869            editor.selections.ranges(cx),
15870            [Point::new(0, 1)..Point::new(0, 1)]
15871        );
15872    });
15873}
15874
15875#[gpui::test]
15876fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
15877    init_test(cx, |_| {});
15878
15879    let markers = vec![('[', ']').into(), ('(', ')').into()];
15880    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
15881        indoc! {"
15882            [aaaa
15883            (bbbb]
15884            cccc)",
15885        },
15886        markers.clone(),
15887    );
15888    let excerpt_ranges = markers.into_iter().map(|marker| {
15889        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
15890        ExcerptRange::new(context)
15891    });
15892    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
15893    let multibuffer = cx.new(|cx| {
15894        let mut multibuffer = MultiBuffer::new(ReadWrite);
15895        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
15896        multibuffer
15897    });
15898
15899    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
15900    editor.update_in(cx, |editor, window, cx| {
15901        let (expected_text, selection_ranges) = marked_text_ranges(
15902            indoc! {"
15903                aaaa
15904                bˇbbb
15905                bˇbbˇb
15906                cccc"
15907            },
15908            true,
15909        );
15910        assert_eq!(editor.text(cx), expected_text);
15911        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15912            s.select_ranges(selection_ranges)
15913        });
15914
15915        editor.handle_input("X", window, cx);
15916
15917        let (expected_text, expected_selections) = marked_text_ranges(
15918            indoc! {"
15919                aaaa
15920                bXˇbbXb
15921                bXˇbbXˇb
15922                cccc"
15923            },
15924            false,
15925        );
15926        assert_eq!(editor.text(cx), expected_text);
15927        assert_eq!(editor.selections.ranges(cx), expected_selections);
15928
15929        editor.newline(&Newline, window, cx);
15930        let (expected_text, expected_selections) = marked_text_ranges(
15931            indoc! {"
15932                aaaa
15933                bX
15934                ˇbbX
15935                b
15936                bX
15937                ˇbbX
15938                ˇb
15939                cccc"
15940            },
15941            false,
15942        );
15943        assert_eq!(editor.text(cx), expected_text);
15944        assert_eq!(editor.selections.ranges(cx), expected_selections);
15945    });
15946}
15947
15948#[gpui::test]
15949fn test_refresh_selections(cx: &mut TestAppContext) {
15950    init_test(cx, |_| {});
15951
15952    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15953    let mut excerpt1_id = None;
15954    let multibuffer = cx.new(|cx| {
15955        let mut multibuffer = MultiBuffer::new(ReadWrite);
15956        excerpt1_id = multibuffer
15957            .push_excerpts(
15958                buffer.clone(),
15959                [
15960                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
15961                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
15962                ],
15963                cx,
15964            )
15965            .into_iter()
15966            .next();
15967        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
15968        multibuffer
15969    });
15970
15971    let editor = cx.add_window(|window, cx| {
15972        let mut editor = build_editor(multibuffer.clone(), window, cx);
15973        let snapshot = editor.snapshot(window, cx);
15974        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15975            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
15976        });
15977        editor.begin_selection(
15978            Point::new(2, 1).to_display_point(&snapshot),
15979            true,
15980            1,
15981            window,
15982            cx,
15983        );
15984        assert_eq!(
15985            editor.selections.ranges(cx),
15986            [
15987                Point::new(1, 3)..Point::new(1, 3),
15988                Point::new(2, 1)..Point::new(2, 1),
15989            ]
15990        );
15991        editor
15992    });
15993
15994    // Refreshing selections is a no-op when excerpts haven't changed.
15995    _ = editor.update(cx, |editor, window, cx| {
15996        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
15997        assert_eq!(
15998            editor.selections.ranges(cx),
15999            [
16000                Point::new(1, 3)..Point::new(1, 3),
16001                Point::new(2, 1)..Point::new(2, 1),
16002            ]
16003        );
16004    });
16005
16006    multibuffer.update(cx, |multibuffer, cx| {
16007        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
16008    });
16009    _ = editor.update(cx, |editor, window, cx| {
16010        // Removing an excerpt causes the first selection to become degenerate.
16011        assert_eq!(
16012            editor.selections.ranges(cx),
16013            [
16014                Point::new(0, 0)..Point::new(0, 0),
16015                Point::new(0, 1)..Point::new(0, 1)
16016            ]
16017        );
16018
16019        // Refreshing selections will relocate the first selection to the original buffer
16020        // location.
16021        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16022        assert_eq!(
16023            editor.selections.ranges(cx),
16024            [
16025                Point::new(0, 1)..Point::new(0, 1),
16026                Point::new(0, 3)..Point::new(0, 3)
16027            ]
16028        );
16029        assert!(editor.selections.pending_anchor().is_some());
16030    });
16031}
16032
16033#[gpui::test]
16034fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
16035    init_test(cx, |_| {});
16036
16037    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16038    let mut excerpt1_id = None;
16039    let multibuffer = cx.new(|cx| {
16040        let mut multibuffer = MultiBuffer::new(ReadWrite);
16041        excerpt1_id = multibuffer
16042            .push_excerpts(
16043                buffer.clone(),
16044                [
16045                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
16046                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
16047                ],
16048                cx,
16049            )
16050            .into_iter()
16051            .next();
16052        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
16053        multibuffer
16054    });
16055
16056    let editor = cx.add_window(|window, cx| {
16057        let mut editor = build_editor(multibuffer.clone(), window, cx);
16058        let snapshot = editor.snapshot(window, cx);
16059        editor.begin_selection(
16060            Point::new(1, 3).to_display_point(&snapshot),
16061            false,
16062            1,
16063            window,
16064            cx,
16065        );
16066        assert_eq!(
16067            editor.selections.ranges(cx),
16068            [Point::new(1, 3)..Point::new(1, 3)]
16069        );
16070        editor
16071    });
16072
16073    multibuffer.update(cx, |multibuffer, cx| {
16074        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
16075    });
16076    _ = editor.update(cx, |editor, window, cx| {
16077        assert_eq!(
16078            editor.selections.ranges(cx),
16079            [Point::new(0, 0)..Point::new(0, 0)]
16080        );
16081
16082        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
16083        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16084        assert_eq!(
16085            editor.selections.ranges(cx),
16086            [Point::new(0, 3)..Point::new(0, 3)]
16087        );
16088        assert!(editor.selections.pending_anchor().is_some());
16089    });
16090}
16091
16092#[gpui::test]
16093async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
16094    init_test(cx, |_| {});
16095
16096    let language = Arc::new(
16097        Language::new(
16098            LanguageConfig {
16099                brackets: BracketPairConfig {
16100                    pairs: vec![
16101                        BracketPair {
16102                            start: "{".to_string(),
16103                            end: "}".to_string(),
16104                            close: true,
16105                            surround: true,
16106                            newline: true,
16107                        },
16108                        BracketPair {
16109                            start: "/* ".to_string(),
16110                            end: " */".to_string(),
16111                            close: true,
16112                            surround: true,
16113                            newline: true,
16114                        },
16115                    ],
16116                    ..Default::default()
16117                },
16118                ..Default::default()
16119            },
16120            Some(tree_sitter_rust::LANGUAGE.into()),
16121        )
16122        .with_indents_query("")
16123        .unwrap(),
16124    );
16125
16126    let text = concat!(
16127        "{   }\n",     //
16128        "  x\n",       //
16129        "  /*   */\n", //
16130        "x\n",         //
16131        "{{} }\n",     //
16132    );
16133
16134    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
16135    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
16136    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
16137    editor
16138        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
16139        .await;
16140
16141    editor.update_in(cx, |editor, window, cx| {
16142        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16143            s.select_display_ranges([
16144                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
16145                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
16146                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
16147            ])
16148        });
16149        editor.newline(&Newline, window, cx);
16150
16151        assert_eq!(
16152            editor.buffer().read(cx).read(cx).text(),
16153            concat!(
16154                "{ \n",    // Suppress rustfmt
16155                "\n",      //
16156                "}\n",     //
16157                "  x\n",   //
16158                "  /* \n", //
16159                "  \n",    //
16160                "  */\n",  //
16161                "x\n",     //
16162                "{{} \n",  //
16163                "}\n",     //
16164            )
16165        );
16166    });
16167}
16168
16169#[gpui::test]
16170fn test_highlighted_ranges(cx: &mut TestAppContext) {
16171    init_test(cx, |_| {});
16172
16173    let editor = cx.add_window(|window, cx| {
16174        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
16175        build_editor(buffer, window, cx)
16176    });
16177
16178    _ = editor.update(cx, |editor, window, cx| {
16179        struct Type1;
16180        struct Type2;
16181
16182        let buffer = editor.buffer.read(cx).snapshot(cx);
16183
16184        let anchor_range =
16185            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
16186
16187        editor.highlight_background::<Type1>(
16188            &[
16189                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
16190                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
16191                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
16192                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
16193            ],
16194            |_| Hsla::red(),
16195            cx,
16196        );
16197        editor.highlight_background::<Type2>(
16198            &[
16199                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
16200                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
16201                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
16202                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
16203            ],
16204            |_| Hsla::green(),
16205            cx,
16206        );
16207
16208        let snapshot = editor.snapshot(window, cx);
16209        let highlighted_ranges = editor.sorted_background_highlights_in_range(
16210            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
16211            &snapshot,
16212            cx.theme(),
16213        );
16214        assert_eq!(
16215            highlighted_ranges,
16216            &[
16217                (
16218                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
16219                    Hsla::green(),
16220                ),
16221                (
16222                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
16223                    Hsla::red(),
16224                ),
16225                (
16226                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
16227                    Hsla::green(),
16228                ),
16229                (
16230                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16231                    Hsla::red(),
16232                ),
16233            ]
16234        );
16235        assert_eq!(
16236            editor.sorted_background_highlights_in_range(
16237                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
16238                &snapshot,
16239                cx.theme(),
16240            ),
16241            &[(
16242                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16243                Hsla::red(),
16244            )]
16245        );
16246    });
16247}
16248
16249#[gpui::test]
16250async fn test_following(cx: &mut TestAppContext) {
16251    init_test(cx, |_| {});
16252
16253    let fs = FakeFs::new(cx.executor());
16254    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16255
16256    let buffer = project.update(cx, |project, cx| {
16257        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, false, cx);
16258        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
16259    });
16260    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
16261    let follower = cx.update(|cx| {
16262        cx.open_window(
16263            WindowOptions {
16264                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
16265                    gpui::Point::new(px(0.), px(0.)),
16266                    gpui::Point::new(px(10.), px(80.)),
16267                ))),
16268                ..Default::default()
16269            },
16270            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
16271        )
16272        .unwrap()
16273    });
16274
16275    let is_still_following = Rc::new(RefCell::new(true));
16276    let follower_edit_event_count = Rc::new(RefCell::new(0));
16277    let pending_update = Rc::new(RefCell::new(None));
16278    let leader_entity = leader.root(cx).unwrap();
16279    let follower_entity = follower.root(cx).unwrap();
16280    _ = follower.update(cx, {
16281        let update = pending_update.clone();
16282        let is_still_following = is_still_following.clone();
16283        let follower_edit_event_count = follower_edit_event_count.clone();
16284        |_, window, cx| {
16285            cx.subscribe_in(
16286                &leader_entity,
16287                window,
16288                move |_, leader, event, window, cx| {
16289                    leader.read(cx).add_event_to_update_proto(
16290                        event,
16291                        &mut update.borrow_mut(),
16292                        window,
16293                        cx,
16294                    );
16295                },
16296            )
16297            .detach();
16298
16299            cx.subscribe_in(
16300                &follower_entity,
16301                window,
16302                move |_, _, event: &EditorEvent, _window, _cx| {
16303                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
16304                        *is_still_following.borrow_mut() = false;
16305                    }
16306
16307                    if let EditorEvent::BufferEdited = event {
16308                        *follower_edit_event_count.borrow_mut() += 1;
16309                    }
16310                },
16311            )
16312            .detach();
16313        }
16314    });
16315
16316    // Update the selections only
16317    _ = leader.update(cx, |leader, window, cx| {
16318        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16319            s.select_ranges([1..1])
16320        });
16321    });
16322    follower
16323        .update(cx, |follower, window, cx| {
16324            follower.apply_update_proto(
16325                &project,
16326                pending_update.borrow_mut().take().unwrap(),
16327                window,
16328                cx,
16329            )
16330        })
16331        .unwrap()
16332        .await
16333        .unwrap();
16334    _ = follower.update(cx, |follower, _, cx| {
16335        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
16336    });
16337    assert!(*is_still_following.borrow());
16338    assert_eq!(*follower_edit_event_count.borrow(), 0);
16339
16340    // Update the scroll position only
16341    _ = leader.update(cx, |leader, window, cx| {
16342        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16343    });
16344    follower
16345        .update(cx, |follower, window, cx| {
16346            follower.apply_update_proto(
16347                &project,
16348                pending_update.borrow_mut().take().unwrap(),
16349                window,
16350                cx,
16351            )
16352        })
16353        .unwrap()
16354        .await
16355        .unwrap();
16356    assert_eq!(
16357        follower
16358            .update(cx, |follower, _, cx| follower.scroll_position(cx))
16359            .unwrap(),
16360        gpui::Point::new(1.5, 3.5)
16361    );
16362    assert!(*is_still_following.borrow());
16363    assert_eq!(*follower_edit_event_count.borrow(), 0);
16364
16365    // Update the selections and scroll position. The follower's scroll position is updated
16366    // via autoscroll, not via the leader's exact scroll position.
16367    _ = leader.update(cx, |leader, window, cx| {
16368        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16369            s.select_ranges([0..0])
16370        });
16371        leader.request_autoscroll(Autoscroll::newest(), cx);
16372        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16373    });
16374    follower
16375        .update(cx, |follower, window, cx| {
16376            follower.apply_update_proto(
16377                &project,
16378                pending_update.borrow_mut().take().unwrap(),
16379                window,
16380                cx,
16381            )
16382        })
16383        .unwrap()
16384        .await
16385        .unwrap();
16386    _ = follower.update(cx, |follower, _, cx| {
16387        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
16388        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
16389    });
16390    assert!(*is_still_following.borrow());
16391
16392    // Creating a pending selection that precedes another selection
16393    _ = leader.update(cx, |leader, window, cx| {
16394        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16395            s.select_ranges([1..1])
16396        });
16397        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
16398    });
16399    follower
16400        .update(cx, |follower, window, cx| {
16401            follower.apply_update_proto(
16402                &project,
16403                pending_update.borrow_mut().take().unwrap(),
16404                window,
16405                cx,
16406            )
16407        })
16408        .unwrap()
16409        .await
16410        .unwrap();
16411    _ = follower.update(cx, |follower, _, cx| {
16412        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
16413    });
16414    assert!(*is_still_following.borrow());
16415
16416    // Extend the pending selection so that it surrounds another selection
16417    _ = leader.update(cx, |leader, window, cx| {
16418        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
16419    });
16420    follower
16421        .update(cx, |follower, window, cx| {
16422            follower.apply_update_proto(
16423                &project,
16424                pending_update.borrow_mut().take().unwrap(),
16425                window,
16426                cx,
16427            )
16428        })
16429        .unwrap()
16430        .await
16431        .unwrap();
16432    _ = follower.update(cx, |follower, _, cx| {
16433        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
16434    });
16435
16436    // Scrolling locally breaks the follow
16437    _ = follower.update(cx, |follower, window, cx| {
16438        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
16439        follower.set_scroll_anchor(
16440            ScrollAnchor {
16441                anchor: top_anchor,
16442                offset: gpui::Point::new(0.0, 0.5),
16443            },
16444            window,
16445            cx,
16446        );
16447    });
16448    assert!(!(*is_still_following.borrow()));
16449}
16450
16451#[gpui::test]
16452async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
16453    init_test(cx, |_| {});
16454
16455    let fs = FakeFs::new(cx.executor());
16456    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16457    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16458    let pane = workspace
16459        .update(cx, |workspace, _, _| workspace.active_pane().clone())
16460        .unwrap();
16461
16462    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16463
16464    let leader = pane.update_in(cx, |_, window, cx| {
16465        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
16466        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
16467    });
16468
16469    // Start following the editor when it has no excerpts.
16470    let mut state_message =
16471        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16472    let workspace_entity = workspace.root(cx).unwrap();
16473    let follower_1 = cx
16474        .update_window(*workspace.deref(), |_, window, cx| {
16475            Editor::from_state_proto(
16476                workspace_entity,
16477                ViewId {
16478                    creator: CollaboratorId::PeerId(PeerId::default()),
16479                    id: 0,
16480                },
16481                &mut state_message,
16482                window,
16483                cx,
16484            )
16485        })
16486        .unwrap()
16487        .unwrap()
16488        .await
16489        .unwrap();
16490
16491    let update_message = Rc::new(RefCell::new(None));
16492    follower_1.update_in(cx, {
16493        let update = update_message.clone();
16494        |_, window, cx| {
16495            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
16496                leader.read(cx).add_event_to_update_proto(
16497                    event,
16498                    &mut update.borrow_mut(),
16499                    window,
16500                    cx,
16501                );
16502            })
16503            .detach();
16504        }
16505    });
16506
16507    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
16508        (
16509            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, false, cx),
16510            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, false, cx),
16511        )
16512    });
16513
16514    // Insert some excerpts.
16515    leader.update(cx, |leader, cx| {
16516        leader.buffer.update(cx, |multibuffer, cx| {
16517            multibuffer.set_excerpts_for_path(
16518                PathKey::namespaced(1, rel_path("b.txt").into_arc()),
16519                buffer_1.clone(),
16520                vec![
16521                    Point::row_range(0..3),
16522                    Point::row_range(1..6),
16523                    Point::row_range(12..15),
16524                ],
16525                0,
16526                cx,
16527            );
16528            multibuffer.set_excerpts_for_path(
16529                PathKey::namespaced(1, rel_path("a.txt").into_arc()),
16530                buffer_2.clone(),
16531                vec![Point::row_range(0..6), Point::row_range(8..12)],
16532                0,
16533                cx,
16534            );
16535        });
16536    });
16537
16538    // Apply the update of adding 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    assert_eq!(
16551        follower_1.update(cx, |editor, cx| editor.text(cx)),
16552        leader.update(cx, |editor, cx| editor.text(cx))
16553    );
16554    update_message.borrow_mut().take();
16555
16556    // Start following separately after it already has excerpts.
16557    let mut state_message =
16558        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16559    let workspace_entity = workspace.root(cx).unwrap();
16560    let follower_2 = cx
16561        .update_window(*workspace.deref(), |_, window, cx| {
16562            Editor::from_state_proto(
16563                workspace_entity,
16564                ViewId {
16565                    creator: CollaboratorId::PeerId(PeerId::default()),
16566                    id: 0,
16567                },
16568                &mut state_message,
16569                window,
16570                cx,
16571            )
16572        })
16573        .unwrap()
16574        .unwrap()
16575        .await
16576        .unwrap();
16577    assert_eq!(
16578        follower_2.update(cx, |editor, cx| editor.text(cx)),
16579        leader.update(cx, |editor, cx| editor.text(cx))
16580    );
16581
16582    // Remove some excerpts.
16583    leader.update(cx, |leader, cx| {
16584        leader.buffer.update(cx, |multibuffer, cx| {
16585            let excerpt_ids = multibuffer.excerpt_ids();
16586            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
16587            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
16588        });
16589    });
16590
16591    // Apply the update of removing the excerpts.
16592    follower_1
16593        .update_in(cx, |follower, window, cx| {
16594            follower.apply_update_proto(
16595                &project,
16596                update_message.borrow().clone().unwrap(),
16597                window,
16598                cx,
16599            )
16600        })
16601        .await
16602        .unwrap();
16603    follower_2
16604        .update_in(cx, |follower, window, cx| {
16605            follower.apply_update_proto(
16606                &project,
16607                update_message.borrow().clone().unwrap(),
16608                window,
16609                cx,
16610            )
16611        })
16612        .await
16613        .unwrap();
16614    update_message.borrow_mut().take();
16615    assert_eq!(
16616        follower_1.update(cx, |editor, cx| editor.text(cx)),
16617        leader.update(cx, |editor, cx| editor.text(cx))
16618    );
16619}
16620
16621#[gpui::test]
16622async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16623    init_test(cx, |_| {});
16624
16625    let mut cx = EditorTestContext::new(cx).await;
16626    let lsp_store =
16627        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
16628
16629    cx.set_state(indoc! {"
16630        ˇfn func(abc def: i32) -> u32 {
16631        }
16632    "});
16633
16634    cx.update(|_, cx| {
16635        lsp_store.update(cx, |lsp_store, cx| {
16636            lsp_store
16637                .update_diagnostics(
16638                    LanguageServerId(0),
16639                    lsp::PublishDiagnosticsParams {
16640                        uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
16641                        version: None,
16642                        diagnostics: vec![
16643                            lsp::Diagnostic {
16644                                range: lsp::Range::new(
16645                                    lsp::Position::new(0, 11),
16646                                    lsp::Position::new(0, 12),
16647                                ),
16648                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16649                                ..Default::default()
16650                            },
16651                            lsp::Diagnostic {
16652                                range: lsp::Range::new(
16653                                    lsp::Position::new(0, 12),
16654                                    lsp::Position::new(0, 15),
16655                                ),
16656                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16657                                ..Default::default()
16658                            },
16659                            lsp::Diagnostic {
16660                                range: lsp::Range::new(
16661                                    lsp::Position::new(0, 25),
16662                                    lsp::Position::new(0, 28),
16663                                ),
16664                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16665                                ..Default::default()
16666                            },
16667                        ],
16668                    },
16669                    None,
16670                    DiagnosticSourceKind::Pushed,
16671                    &[],
16672                    cx,
16673                )
16674                .unwrap()
16675        });
16676    });
16677
16678    executor.run_until_parked();
16679
16680    cx.update_editor(|editor, window, cx| {
16681        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16682    });
16683
16684    cx.assert_editor_state(indoc! {"
16685        fn func(abc def: i32) -> ˇu32 {
16686        }
16687    "});
16688
16689    cx.update_editor(|editor, window, cx| {
16690        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16691    });
16692
16693    cx.assert_editor_state(indoc! {"
16694        fn func(abc ˇdef: i32) -> u32 {
16695        }
16696    "});
16697
16698    cx.update_editor(|editor, window, cx| {
16699        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16700    });
16701
16702    cx.assert_editor_state(indoc! {"
16703        fn func(abcˇ def: i32) -> u32 {
16704        }
16705    "});
16706
16707    cx.update_editor(|editor, window, cx| {
16708        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16709    });
16710
16711    cx.assert_editor_state(indoc! {"
16712        fn func(abc def: i32) -> ˇu32 {
16713        }
16714    "});
16715}
16716
16717#[gpui::test]
16718async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16719    init_test(cx, |_| {});
16720
16721    let mut cx = EditorTestContext::new(cx).await;
16722
16723    let diff_base = r#"
16724        use some::mod;
16725
16726        const A: u32 = 42;
16727
16728        fn main() {
16729            println!("hello");
16730
16731            println!("world");
16732        }
16733        "#
16734    .unindent();
16735
16736    // Edits are modified, removed, modified, added
16737    cx.set_state(
16738        &r#"
16739        use some::modified;
16740
16741        ˇ
16742        fn main() {
16743            println!("hello there");
16744
16745            println!("around the");
16746            println!("world");
16747        }
16748        "#
16749        .unindent(),
16750    );
16751
16752    cx.set_head_text(&diff_base);
16753    executor.run_until_parked();
16754
16755    cx.update_editor(|editor, window, cx| {
16756        //Wrap around the bottom of the buffer
16757        for _ in 0..3 {
16758            editor.go_to_next_hunk(&GoToHunk, window, cx);
16759        }
16760    });
16761
16762    cx.assert_editor_state(
16763        &r#"
16764        ˇuse some::modified;
16765
16766
16767        fn main() {
16768            println!("hello there");
16769
16770            println!("around the");
16771            println!("world");
16772        }
16773        "#
16774        .unindent(),
16775    );
16776
16777    cx.update_editor(|editor, window, cx| {
16778        //Wrap around the top of the buffer
16779        for _ in 0..2 {
16780            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16781        }
16782    });
16783
16784    cx.assert_editor_state(
16785        &r#"
16786        use some::modified;
16787
16788
16789        fn main() {
16790        ˇ    println!("hello there");
16791
16792            println!("around the");
16793            println!("world");
16794        }
16795        "#
16796        .unindent(),
16797    );
16798
16799    cx.update_editor(|editor, window, cx| {
16800        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16801    });
16802
16803    cx.assert_editor_state(
16804        &r#"
16805        use some::modified;
16806
16807        ˇ
16808        fn main() {
16809            println!("hello there");
16810
16811            println!("around the");
16812            println!("world");
16813        }
16814        "#
16815        .unindent(),
16816    );
16817
16818    cx.update_editor(|editor, window, cx| {
16819        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16820    });
16821
16822    cx.assert_editor_state(
16823        &r#"
16824        ˇuse some::modified;
16825
16826
16827        fn main() {
16828            println!("hello there");
16829
16830            println!("around the");
16831            println!("world");
16832        }
16833        "#
16834        .unindent(),
16835    );
16836
16837    cx.update_editor(|editor, window, cx| {
16838        for _ in 0..2 {
16839            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16840        }
16841    });
16842
16843    cx.assert_editor_state(
16844        &r#"
16845        use some::modified;
16846
16847
16848        fn main() {
16849        ˇ    println!("hello there");
16850
16851            println!("around the");
16852            println!("world");
16853        }
16854        "#
16855        .unindent(),
16856    );
16857
16858    cx.update_editor(|editor, window, cx| {
16859        editor.fold(&Fold, window, cx);
16860    });
16861
16862    cx.update_editor(|editor, window, cx| {
16863        editor.go_to_next_hunk(&GoToHunk, window, cx);
16864    });
16865
16866    cx.assert_editor_state(
16867        &r#"
16868        ˇuse some::modified;
16869
16870
16871        fn main() {
16872            println!("hello there");
16873
16874            println!("around the");
16875            println!("world");
16876        }
16877        "#
16878        .unindent(),
16879    );
16880}
16881
16882#[test]
16883fn test_split_words() {
16884    fn split(text: &str) -> Vec<&str> {
16885        split_words(text).collect()
16886    }
16887
16888    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
16889    assert_eq!(split("hello_world"), &["hello_", "world"]);
16890    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
16891    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
16892    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
16893    assert_eq!(split("helloworld"), &["helloworld"]);
16894
16895    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
16896}
16897
16898#[gpui::test]
16899async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
16900    init_test(cx, |_| {});
16901
16902    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
16903    let mut assert = |before, after| {
16904        let _state_context = cx.set_state(before);
16905        cx.run_until_parked();
16906        cx.update_editor(|editor, window, cx| {
16907            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
16908        });
16909        cx.run_until_parked();
16910        cx.assert_editor_state(after);
16911    };
16912
16913    // Outside bracket jumps to outside of matching bracket
16914    assert("console.logˇ(var);", "console.log(var)ˇ;");
16915    assert("console.log(var)ˇ;", "console.logˇ(var);");
16916
16917    // Inside bracket jumps to inside of matching bracket
16918    assert("console.log(ˇvar);", "console.log(varˇ);");
16919    assert("console.log(varˇ);", "console.log(ˇvar);");
16920
16921    // When outside a bracket and inside, favor jumping to the inside bracket
16922    assert(
16923        "console.log('foo', [1, 2, 3]ˇ);",
16924        "console.log(ˇ'foo', [1, 2, 3]);",
16925    );
16926    assert(
16927        "console.log(ˇ'foo', [1, 2, 3]);",
16928        "console.log('foo', [1, 2, 3]ˇ);",
16929    );
16930
16931    // Bias forward if two options are equally likely
16932    assert(
16933        "let result = curried_fun()ˇ();",
16934        "let result = curried_fun()()ˇ;",
16935    );
16936
16937    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
16938    assert(
16939        indoc! {"
16940            function test() {
16941                console.log('test')ˇ
16942            }"},
16943        indoc! {"
16944            function test() {
16945                console.logˇ('test')
16946            }"},
16947    );
16948}
16949
16950#[gpui::test]
16951async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
16952    init_test(cx, |_| {});
16953
16954    let fs = FakeFs::new(cx.executor());
16955    fs.insert_tree(
16956        path!("/a"),
16957        json!({
16958            "main.rs": "fn main() { let a = 5; }",
16959            "other.rs": "// Test file",
16960        }),
16961    )
16962    .await;
16963    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16964
16965    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
16966    language_registry.add(Arc::new(Language::new(
16967        LanguageConfig {
16968            name: "Rust".into(),
16969            matcher: LanguageMatcher {
16970                path_suffixes: vec!["rs".to_string()],
16971                ..Default::default()
16972            },
16973            brackets: BracketPairConfig {
16974                pairs: vec![BracketPair {
16975                    start: "{".to_string(),
16976                    end: "}".to_string(),
16977                    close: true,
16978                    surround: true,
16979                    newline: true,
16980                }],
16981                disabled_scopes_by_bracket_ix: Vec::new(),
16982            },
16983            ..Default::default()
16984        },
16985        Some(tree_sitter_rust::LANGUAGE.into()),
16986    )));
16987    let mut fake_servers = language_registry.register_fake_lsp(
16988        "Rust",
16989        FakeLspAdapter {
16990            capabilities: lsp::ServerCapabilities {
16991                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
16992                    first_trigger_character: "{".to_string(),
16993                    more_trigger_character: None,
16994                }),
16995                ..Default::default()
16996            },
16997            ..Default::default()
16998        },
16999    );
17000
17001    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17002
17003    let cx = &mut VisualTestContext::from_window(*workspace, cx);
17004
17005    let worktree_id = workspace
17006        .update(cx, |workspace, _, cx| {
17007            workspace.project().update(cx, |project, cx| {
17008                project.worktrees(cx).next().unwrap().read(cx).id()
17009            })
17010        })
17011        .unwrap();
17012
17013    let buffer = project
17014        .update(cx, |project, cx| {
17015            project.open_local_buffer(path!("/a/main.rs"), cx)
17016        })
17017        .await
17018        .unwrap();
17019    let editor_handle = workspace
17020        .update(cx, |workspace, window, cx| {
17021            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
17022        })
17023        .unwrap()
17024        .await
17025        .unwrap()
17026        .downcast::<Editor>()
17027        .unwrap();
17028
17029    cx.executor().start_waiting();
17030    let fake_server = fake_servers.next().await.unwrap();
17031
17032    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
17033        |params, _| async move {
17034            assert_eq!(
17035                params.text_document_position.text_document.uri,
17036                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
17037            );
17038            assert_eq!(
17039                params.text_document_position.position,
17040                lsp::Position::new(0, 21),
17041            );
17042
17043            Ok(Some(vec![lsp::TextEdit {
17044                new_text: "]".to_string(),
17045                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17046            }]))
17047        },
17048    );
17049
17050    editor_handle.update_in(cx, |editor, window, cx| {
17051        window.focus(&editor.focus_handle(cx));
17052        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17053            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
17054        });
17055        editor.handle_input("{", window, cx);
17056    });
17057
17058    cx.executor().run_until_parked();
17059
17060    buffer.update(cx, |buffer, _| {
17061        assert_eq!(
17062            buffer.text(),
17063            "fn main() { let a = {5}; }",
17064            "No extra braces from on type formatting should appear in the buffer"
17065        )
17066    });
17067}
17068
17069#[gpui::test(iterations = 20, seeds(31))]
17070async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
17071    init_test(cx, |_| {});
17072
17073    let mut cx = EditorLspTestContext::new_rust(
17074        lsp::ServerCapabilities {
17075            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
17076                first_trigger_character: ".".to_string(),
17077                more_trigger_character: None,
17078            }),
17079            ..Default::default()
17080        },
17081        cx,
17082    )
17083    .await;
17084
17085    cx.update_buffer(|buffer, _| {
17086        // This causes autoindent to be async.
17087        buffer.set_sync_parse_timeout(Duration::ZERO)
17088    });
17089
17090    cx.set_state("fn c() {\n    d()ˇ\n}\n");
17091    cx.simulate_keystroke("\n");
17092    cx.run_until_parked();
17093
17094    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
17095    let mut request =
17096        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
17097            let buffer_cloned = buffer_cloned.clone();
17098            async move {
17099                buffer_cloned.update(&mut cx, |buffer, _| {
17100                    assert_eq!(
17101                        buffer.text(),
17102                        "fn c() {\n    d()\n        .\n}\n",
17103                        "OnTypeFormatting should triggered after autoindent applied"
17104                    )
17105                })?;
17106
17107                Ok(Some(vec![]))
17108            }
17109        });
17110
17111    cx.simulate_keystroke(".");
17112    cx.run_until_parked();
17113
17114    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
17115    assert!(request.next().await.is_some());
17116    request.close();
17117    assert!(request.next().await.is_none());
17118}
17119
17120#[gpui::test]
17121async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
17122    init_test(cx, |_| {});
17123
17124    let fs = FakeFs::new(cx.executor());
17125    fs.insert_tree(
17126        path!("/a"),
17127        json!({
17128            "main.rs": "fn main() { let a = 5; }",
17129            "other.rs": "// Test file",
17130        }),
17131    )
17132    .await;
17133
17134    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17135
17136    let server_restarts = Arc::new(AtomicUsize::new(0));
17137    let closure_restarts = Arc::clone(&server_restarts);
17138    let language_server_name = "test language server";
17139    let language_name: LanguageName = "Rust".into();
17140
17141    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17142    language_registry.add(Arc::new(Language::new(
17143        LanguageConfig {
17144            name: language_name.clone(),
17145            matcher: LanguageMatcher {
17146                path_suffixes: vec!["rs".to_string()],
17147                ..Default::default()
17148            },
17149            ..Default::default()
17150        },
17151        Some(tree_sitter_rust::LANGUAGE.into()),
17152    )));
17153    let mut fake_servers = language_registry.register_fake_lsp(
17154        "Rust",
17155        FakeLspAdapter {
17156            name: language_server_name,
17157            initialization_options: Some(json!({
17158                "testOptionValue": true
17159            })),
17160            initializer: Some(Box::new(move |fake_server| {
17161                let task_restarts = Arc::clone(&closure_restarts);
17162                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
17163                    task_restarts.fetch_add(1, atomic::Ordering::Release);
17164                    futures::future::ready(Ok(()))
17165                });
17166            })),
17167            ..Default::default()
17168        },
17169    );
17170
17171    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17172    let _buffer = project
17173        .update(cx, |project, cx| {
17174            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
17175        })
17176        .await
17177        .unwrap();
17178    let _fake_server = fake_servers.next().await.unwrap();
17179    update_test_language_settings(cx, |language_settings| {
17180        language_settings.languages.0.insert(
17181            language_name.clone().0,
17182            LanguageSettingsContent {
17183                tab_size: NonZeroU32::new(8),
17184                ..Default::default()
17185            },
17186        );
17187    });
17188    cx.executor().run_until_parked();
17189    assert_eq!(
17190        server_restarts.load(atomic::Ordering::Acquire),
17191        0,
17192        "Should not restart LSP server on an unrelated change"
17193    );
17194
17195    update_test_project_settings(cx, |project_settings| {
17196        project_settings.lsp.insert(
17197            "Some other server name".into(),
17198            LspSettings {
17199                binary: None,
17200                settings: None,
17201                initialization_options: Some(json!({
17202                    "some other init value": false
17203                })),
17204                enable_lsp_tasks: false,
17205                fetch: None,
17206            },
17207        );
17208    });
17209    cx.executor().run_until_parked();
17210    assert_eq!(
17211        server_restarts.load(atomic::Ordering::Acquire),
17212        0,
17213        "Should not restart LSP server on an unrelated LSP settings change"
17214    );
17215
17216    update_test_project_settings(cx, |project_settings| {
17217        project_settings.lsp.insert(
17218            language_server_name.into(),
17219            LspSettings {
17220                binary: None,
17221                settings: None,
17222                initialization_options: Some(json!({
17223                    "anotherInitValue": false
17224                })),
17225                enable_lsp_tasks: false,
17226                fetch: None,
17227            },
17228        );
17229    });
17230    cx.executor().run_until_parked();
17231    assert_eq!(
17232        server_restarts.load(atomic::Ordering::Acquire),
17233        1,
17234        "Should restart LSP server on a related LSP settings change"
17235    );
17236
17237    update_test_project_settings(cx, |project_settings| {
17238        project_settings.lsp.insert(
17239            language_server_name.into(),
17240            LspSettings {
17241                binary: None,
17242                settings: None,
17243                initialization_options: Some(json!({
17244                    "anotherInitValue": false
17245                })),
17246                enable_lsp_tasks: false,
17247                fetch: None,
17248            },
17249        );
17250    });
17251    cx.executor().run_until_parked();
17252    assert_eq!(
17253        server_restarts.load(atomic::Ordering::Acquire),
17254        1,
17255        "Should not restart LSP server on a related LSP settings change that is the same"
17256    );
17257
17258    update_test_project_settings(cx, |project_settings| {
17259        project_settings.lsp.insert(
17260            language_server_name.into(),
17261            LspSettings {
17262                binary: None,
17263                settings: None,
17264                initialization_options: None,
17265                enable_lsp_tasks: false,
17266                fetch: None,
17267            },
17268        );
17269    });
17270    cx.executor().run_until_parked();
17271    assert_eq!(
17272        server_restarts.load(atomic::Ordering::Acquire),
17273        2,
17274        "Should restart LSP server on another related LSP settings change"
17275    );
17276}
17277
17278#[gpui::test]
17279async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
17280    init_test(cx, |_| {});
17281
17282    let mut cx = EditorLspTestContext::new_rust(
17283        lsp::ServerCapabilities {
17284            completion_provider: Some(lsp::CompletionOptions {
17285                trigger_characters: Some(vec![".".to_string()]),
17286                resolve_provider: Some(true),
17287                ..Default::default()
17288            }),
17289            ..Default::default()
17290        },
17291        cx,
17292    )
17293    .await;
17294
17295    cx.set_state("fn main() { let a = 2ˇ; }");
17296    cx.simulate_keystroke(".");
17297    let completion_item = lsp::CompletionItem {
17298        label: "some".into(),
17299        kind: Some(lsp::CompletionItemKind::SNIPPET),
17300        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
17301        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
17302            kind: lsp::MarkupKind::Markdown,
17303            value: "```rust\nSome(2)\n```".to_string(),
17304        })),
17305        deprecated: Some(false),
17306        sort_text: Some("fffffff2".to_string()),
17307        filter_text: Some("some".to_string()),
17308        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
17309        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17310            range: lsp::Range {
17311                start: lsp::Position {
17312                    line: 0,
17313                    character: 22,
17314                },
17315                end: lsp::Position {
17316                    line: 0,
17317                    character: 22,
17318                },
17319            },
17320            new_text: "Some(2)".to_string(),
17321        })),
17322        additional_text_edits: Some(vec![lsp::TextEdit {
17323            range: lsp::Range {
17324                start: lsp::Position {
17325                    line: 0,
17326                    character: 20,
17327                },
17328                end: lsp::Position {
17329                    line: 0,
17330                    character: 22,
17331                },
17332            },
17333            new_text: "".to_string(),
17334        }]),
17335        ..Default::default()
17336    };
17337
17338    let closure_completion_item = completion_item.clone();
17339    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17340        let task_completion_item = closure_completion_item.clone();
17341        async move {
17342            Ok(Some(lsp::CompletionResponse::Array(vec![
17343                task_completion_item,
17344            ])))
17345        }
17346    });
17347
17348    request.next().await;
17349
17350    cx.condition(|editor, _| editor.context_menu_visible())
17351        .await;
17352    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
17353        editor
17354            .confirm_completion(&ConfirmCompletion::default(), window, cx)
17355            .unwrap()
17356    });
17357    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
17358
17359    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
17360        let task_completion_item = completion_item.clone();
17361        async move { Ok(task_completion_item) }
17362    })
17363    .next()
17364    .await
17365    .unwrap();
17366    apply_additional_edits.await.unwrap();
17367    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
17368}
17369
17370#[gpui::test]
17371async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
17372    init_test(cx, |_| {});
17373
17374    let mut cx = EditorLspTestContext::new_rust(
17375        lsp::ServerCapabilities {
17376            completion_provider: Some(lsp::CompletionOptions {
17377                trigger_characters: Some(vec![".".to_string()]),
17378                resolve_provider: Some(true),
17379                ..Default::default()
17380            }),
17381            ..Default::default()
17382        },
17383        cx,
17384    )
17385    .await;
17386
17387    cx.set_state("fn main() { let a = 2ˇ; }");
17388    cx.simulate_keystroke(".");
17389
17390    let item1 = lsp::CompletionItem {
17391        label: "method id()".to_string(),
17392        filter_text: Some("id".to_string()),
17393        detail: None,
17394        documentation: None,
17395        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17396            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17397            new_text: ".id".to_string(),
17398        })),
17399        ..lsp::CompletionItem::default()
17400    };
17401
17402    let item2 = lsp::CompletionItem {
17403        label: "other".to_string(),
17404        filter_text: Some("other".to_string()),
17405        detail: None,
17406        documentation: None,
17407        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17408            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17409            new_text: ".other".to_string(),
17410        })),
17411        ..lsp::CompletionItem::default()
17412    };
17413
17414    let item1 = item1.clone();
17415    cx.set_request_handler::<lsp::request::Completion, _, _>({
17416        let item1 = item1.clone();
17417        move |_, _, _| {
17418            let item1 = item1.clone();
17419            let item2 = item2.clone();
17420            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
17421        }
17422    })
17423    .next()
17424    .await;
17425
17426    cx.condition(|editor, _| editor.context_menu_visible())
17427        .await;
17428    cx.update_editor(|editor, _, _| {
17429        let context_menu = editor.context_menu.borrow_mut();
17430        let context_menu = context_menu
17431            .as_ref()
17432            .expect("Should have the context menu deployed");
17433        match context_menu {
17434            CodeContextMenu::Completions(completions_menu) => {
17435                let completions = completions_menu.completions.borrow_mut();
17436                assert_eq!(
17437                    completions
17438                        .iter()
17439                        .map(|completion| &completion.label.text)
17440                        .collect::<Vec<_>>(),
17441                    vec!["method id()", "other"]
17442                )
17443            }
17444            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17445        }
17446    });
17447
17448    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
17449        let item1 = item1.clone();
17450        move |_, item_to_resolve, _| {
17451            let item1 = item1.clone();
17452            async move {
17453                if item1 == item_to_resolve {
17454                    Ok(lsp::CompletionItem {
17455                        label: "method id()".to_string(),
17456                        filter_text: Some("id".to_string()),
17457                        detail: Some("Now resolved!".to_string()),
17458                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
17459                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17460                            range: lsp::Range::new(
17461                                lsp::Position::new(0, 22),
17462                                lsp::Position::new(0, 22),
17463                            ),
17464                            new_text: ".id".to_string(),
17465                        })),
17466                        ..lsp::CompletionItem::default()
17467                    })
17468                } else {
17469                    Ok(item_to_resolve)
17470                }
17471            }
17472        }
17473    })
17474    .next()
17475    .await
17476    .unwrap();
17477    cx.run_until_parked();
17478
17479    cx.update_editor(|editor, window, cx| {
17480        editor.context_menu_next(&Default::default(), window, cx);
17481    });
17482
17483    cx.update_editor(|editor, _, _| {
17484        let context_menu = editor.context_menu.borrow_mut();
17485        let context_menu = context_menu
17486            .as_ref()
17487            .expect("Should have the context menu deployed");
17488        match context_menu {
17489            CodeContextMenu::Completions(completions_menu) => {
17490                let completions = completions_menu.completions.borrow_mut();
17491                assert_eq!(
17492                    completions
17493                        .iter()
17494                        .map(|completion| &completion.label.text)
17495                        .collect::<Vec<_>>(),
17496                    vec!["method id() Now resolved!", "other"],
17497                    "Should update first completion label, but not second as the filter text did not match."
17498                );
17499            }
17500            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17501        }
17502    });
17503}
17504
17505#[gpui::test]
17506async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
17507    init_test(cx, |_| {});
17508    let mut cx = EditorLspTestContext::new_rust(
17509        lsp::ServerCapabilities {
17510            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
17511            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
17512            completion_provider: Some(lsp::CompletionOptions {
17513                resolve_provider: Some(true),
17514                ..Default::default()
17515            }),
17516            ..Default::default()
17517        },
17518        cx,
17519    )
17520    .await;
17521    cx.set_state(indoc! {"
17522        struct TestStruct {
17523            field: i32
17524        }
17525
17526        fn mainˇ() {
17527            let unused_var = 42;
17528            let test_struct = TestStruct { field: 42 };
17529        }
17530    "});
17531    let symbol_range = cx.lsp_range(indoc! {"
17532        struct TestStruct {
17533            field: i32
17534        }
17535
17536        «fn main»() {
17537            let unused_var = 42;
17538            let test_struct = TestStruct { field: 42 };
17539        }
17540    "});
17541    let mut hover_requests =
17542        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
17543            Ok(Some(lsp::Hover {
17544                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
17545                    kind: lsp::MarkupKind::Markdown,
17546                    value: "Function documentation".to_string(),
17547                }),
17548                range: Some(symbol_range),
17549            }))
17550        });
17551
17552    // Case 1: Test that code action menu hide hover popover
17553    cx.dispatch_action(Hover);
17554    hover_requests.next().await;
17555    cx.condition(|editor, _| editor.hover_state.visible()).await;
17556    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
17557        move |_, _, _| async move {
17558            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
17559                lsp::CodeAction {
17560                    title: "Remove unused variable".to_string(),
17561                    kind: Some(CodeActionKind::QUICKFIX),
17562                    edit: Some(lsp::WorkspaceEdit {
17563                        changes: Some(
17564                            [(
17565                                lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
17566                                vec![lsp::TextEdit {
17567                                    range: lsp::Range::new(
17568                                        lsp::Position::new(5, 4),
17569                                        lsp::Position::new(5, 27),
17570                                    ),
17571                                    new_text: "".to_string(),
17572                                }],
17573                            )]
17574                            .into_iter()
17575                            .collect(),
17576                        ),
17577                        ..Default::default()
17578                    }),
17579                    ..Default::default()
17580                },
17581            )]))
17582        },
17583    );
17584    cx.update_editor(|editor, window, cx| {
17585        editor.toggle_code_actions(
17586            &ToggleCodeActions {
17587                deployed_from: None,
17588                quick_launch: false,
17589            },
17590            window,
17591            cx,
17592        );
17593    });
17594    code_action_requests.next().await;
17595    cx.run_until_parked();
17596    cx.condition(|editor, _| editor.context_menu_visible())
17597        .await;
17598    cx.update_editor(|editor, _, _| {
17599        assert!(
17600            !editor.hover_state.visible(),
17601            "Hover popover should be hidden when code action menu is shown"
17602        );
17603        // Hide code actions
17604        editor.context_menu.take();
17605    });
17606
17607    // Case 2: Test that code completions hide hover popover
17608    cx.dispatch_action(Hover);
17609    hover_requests.next().await;
17610    cx.condition(|editor, _| editor.hover_state.visible()).await;
17611    let counter = Arc::new(AtomicUsize::new(0));
17612    let mut completion_requests =
17613        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17614            let counter = counter.clone();
17615            async move {
17616                counter.fetch_add(1, atomic::Ordering::Release);
17617                Ok(Some(lsp::CompletionResponse::Array(vec![
17618                    lsp::CompletionItem {
17619                        label: "main".into(),
17620                        kind: Some(lsp::CompletionItemKind::FUNCTION),
17621                        detail: Some("() -> ()".to_string()),
17622                        ..Default::default()
17623                    },
17624                    lsp::CompletionItem {
17625                        label: "TestStruct".into(),
17626                        kind: Some(lsp::CompletionItemKind::STRUCT),
17627                        detail: Some("struct TestStruct".to_string()),
17628                        ..Default::default()
17629                    },
17630                ])))
17631            }
17632        });
17633    cx.update_editor(|editor, window, cx| {
17634        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
17635    });
17636    completion_requests.next().await;
17637    cx.condition(|editor, _| editor.context_menu_visible())
17638        .await;
17639    cx.update_editor(|editor, _, _| {
17640        assert!(
17641            !editor.hover_state.visible(),
17642            "Hover popover should be hidden when completion menu is shown"
17643        );
17644    });
17645}
17646
17647#[gpui::test]
17648async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
17649    init_test(cx, |_| {});
17650
17651    let mut cx = EditorLspTestContext::new_rust(
17652        lsp::ServerCapabilities {
17653            completion_provider: Some(lsp::CompletionOptions {
17654                trigger_characters: Some(vec![".".to_string()]),
17655                resolve_provider: Some(true),
17656                ..Default::default()
17657            }),
17658            ..Default::default()
17659        },
17660        cx,
17661    )
17662    .await;
17663
17664    cx.set_state("fn main() { let a = 2ˇ; }");
17665    cx.simulate_keystroke(".");
17666
17667    let unresolved_item_1 = lsp::CompletionItem {
17668        label: "id".to_string(),
17669        filter_text: Some("id".to_string()),
17670        detail: None,
17671        documentation: None,
17672        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17673            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17674            new_text: ".id".to_string(),
17675        })),
17676        ..lsp::CompletionItem::default()
17677    };
17678    let resolved_item_1 = lsp::CompletionItem {
17679        additional_text_edits: Some(vec![lsp::TextEdit {
17680            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
17681            new_text: "!!".to_string(),
17682        }]),
17683        ..unresolved_item_1.clone()
17684    };
17685    let unresolved_item_2 = lsp::CompletionItem {
17686        label: "other".to_string(),
17687        filter_text: Some("other".to_string()),
17688        detail: None,
17689        documentation: None,
17690        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17691            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17692            new_text: ".other".to_string(),
17693        })),
17694        ..lsp::CompletionItem::default()
17695    };
17696    let resolved_item_2 = lsp::CompletionItem {
17697        additional_text_edits: Some(vec![lsp::TextEdit {
17698            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
17699            new_text: "??".to_string(),
17700        }]),
17701        ..unresolved_item_2.clone()
17702    };
17703
17704    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
17705    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
17706    cx.lsp
17707        .server
17708        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
17709            let unresolved_item_1 = unresolved_item_1.clone();
17710            let resolved_item_1 = resolved_item_1.clone();
17711            let unresolved_item_2 = unresolved_item_2.clone();
17712            let resolved_item_2 = resolved_item_2.clone();
17713            let resolve_requests_1 = resolve_requests_1.clone();
17714            let resolve_requests_2 = resolve_requests_2.clone();
17715            move |unresolved_request, _| {
17716                let unresolved_item_1 = unresolved_item_1.clone();
17717                let resolved_item_1 = resolved_item_1.clone();
17718                let unresolved_item_2 = unresolved_item_2.clone();
17719                let resolved_item_2 = resolved_item_2.clone();
17720                let resolve_requests_1 = resolve_requests_1.clone();
17721                let resolve_requests_2 = resolve_requests_2.clone();
17722                async move {
17723                    if unresolved_request == unresolved_item_1 {
17724                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
17725                        Ok(resolved_item_1.clone())
17726                    } else if unresolved_request == unresolved_item_2 {
17727                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
17728                        Ok(resolved_item_2.clone())
17729                    } else {
17730                        panic!("Unexpected completion item {unresolved_request:?}")
17731                    }
17732                }
17733            }
17734        })
17735        .detach();
17736
17737    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17738        let unresolved_item_1 = unresolved_item_1.clone();
17739        let unresolved_item_2 = unresolved_item_2.clone();
17740        async move {
17741            Ok(Some(lsp::CompletionResponse::Array(vec![
17742                unresolved_item_1,
17743                unresolved_item_2,
17744            ])))
17745        }
17746    })
17747    .next()
17748    .await;
17749
17750    cx.condition(|editor, _| editor.context_menu_visible())
17751        .await;
17752    cx.update_editor(|editor, _, _| {
17753        let context_menu = editor.context_menu.borrow_mut();
17754        let context_menu = context_menu
17755            .as_ref()
17756            .expect("Should have the context menu deployed");
17757        match context_menu {
17758            CodeContextMenu::Completions(completions_menu) => {
17759                let completions = completions_menu.completions.borrow_mut();
17760                assert_eq!(
17761                    completions
17762                        .iter()
17763                        .map(|completion| &completion.label.text)
17764                        .collect::<Vec<_>>(),
17765                    vec!["id", "other"]
17766                )
17767            }
17768            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17769        }
17770    });
17771    cx.run_until_parked();
17772
17773    cx.update_editor(|editor, window, cx| {
17774        editor.context_menu_next(&ContextMenuNext, window, cx);
17775    });
17776    cx.run_until_parked();
17777    cx.update_editor(|editor, window, cx| {
17778        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
17779    });
17780    cx.run_until_parked();
17781    cx.update_editor(|editor, window, cx| {
17782        editor.context_menu_next(&ContextMenuNext, window, cx);
17783    });
17784    cx.run_until_parked();
17785    cx.update_editor(|editor, window, cx| {
17786        editor
17787            .compose_completion(&ComposeCompletion::default(), window, cx)
17788            .expect("No task returned")
17789    })
17790    .await
17791    .expect("Completion failed");
17792    cx.run_until_parked();
17793
17794    cx.update_editor(|editor, _, cx| {
17795        assert_eq!(
17796            resolve_requests_1.load(atomic::Ordering::Acquire),
17797            1,
17798            "Should always resolve once despite multiple selections"
17799        );
17800        assert_eq!(
17801            resolve_requests_2.load(atomic::Ordering::Acquire),
17802            1,
17803            "Should always resolve once after multiple selections and applying the completion"
17804        );
17805        assert_eq!(
17806            editor.text(cx),
17807            "fn main() { let a = ??.other; }",
17808            "Should use resolved data when applying the completion"
17809        );
17810    });
17811}
17812
17813#[gpui::test]
17814async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
17815    init_test(cx, |_| {});
17816
17817    let item_0 = lsp::CompletionItem {
17818        label: "abs".into(),
17819        insert_text: Some("abs".into()),
17820        data: Some(json!({ "very": "special"})),
17821        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
17822        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
17823            lsp::InsertReplaceEdit {
17824                new_text: "abs".to_string(),
17825                insert: lsp::Range::default(),
17826                replace: lsp::Range::default(),
17827            },
17828        )),
17829        ..lsp::CompletionItem::default()
17830    };
17831    let items = iter::once(item_0.clone())
17832        .chain((11..51).map(|i| lsp::CompletionItem {
17833            label: format!("item_{}", i),
17834            insert_text: Some(format!("item_{}", i)),
17835            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
17836            ..lsp::CompletionItem::default()
17837        }))
17838        .collect::<Vec<_>>();
17839
17840    let default_commit_characters = vec!["?".to_string()];
17841    let default_data = json!({ "default": "data"});
17842    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
17843    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
17844    let default_edit_range = lsp::Range {
17845        start: lsp::Position {
17846            line: 0,
17847            character: 5,
17848        },
17849        end: lsp::Position {
17850            line: 0,
17851            character: 5,
17852        },
17853    };
17854
17855    let mut cx = EditorLspTestContext::new_rust(
17856        lsp::ServerCapabilities {
17857            completion_provider: Some(lsp::CompletionOptions {
17858                trigger_characters: Some(vec![".".to_string()]),
17859                resolve_provider: Some(true),
17860                ..Default::default()
17861            }),
17862            ..Default::default()
17863        },
17864        cx,
17865    )
17866    .await;
17867
17868    cx.set_state("fn main() { let a = 2ˇ; }");
17869    cx.simulate_keystroke(".");
17870
17871    let completion_data = default_data.clone();
17872    let completion_characters = default_commit_characters.clone();
17873    let completion_items = items.clone();
17874    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17875        let default_data = completion_data.clone();
17876        let default_commit_characters = completion_characters.clone();
17877        let items = completion_items.clone();
17878        async move {
17879            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
17880                items,
17881                item_defaults: Some(lsp::CompletionListItemDefaults {
17882                    data: Some(default_data.clone()),
17883                    commit_characters: Some(default_commit_characters.clone()),
17884                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
17885                        default_edit_range,
17886                    )),
17887                    insert_text_format: Some(default_insert_text_format),
17888                    insert_text_mode: Some(default_insert_text_mode),
17889                }),
17890                ..lsp::CompletionList::default()
17891            })))
17892        }
17893    })
17894    .next()
17895    .await;
17896
17897    let resolved_items = Arc::new(Mutex::new(Vec::new()));
17898    cx.lsp
17899        .server
17900        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
17901            let closure_resolved_items = resolved_items.clone();
17902            move |item_to_resolve, _| {
17903                let closure_resolved_items = closure_resolved_items.clone();
17904                async move {
17905                    closure_resolved_items.lock().push(item_to_resolve.clone());
17906                    Ok(item_to_resolve)
17907                }
17908            }
17909        })
17910        .detach();
17911
17912    cx.condition(|editor, _| editor.context_menu_visible())
17913        .await;
17914    cx.run_until_parked();
17915    cx.update_editor(|editor, _, _| {
17916        let menu = editor.context_menu.borrow_mut();
17917        match menu.as_ref().expect("should have the completions menu") {
17918            CodeContextMenu::Completions(completions_menu) => {
17919                assert_eq!(
17920                    completions_menu
17921                        .entries
17922                        .borrow()
17923                        .iter()
17924                        .map(|mat| mat.string.clone())
17925                        .collect::<Vec<String>>(),
17926                    items
17927                        .iter()
17928                        .map(|completion| completion.label.clone())
17929                        .collect::<Vec<String>>()
17930                );
17931            }
17932            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
17933        }
17934    });
17935    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
17936    // with 4 from the end.
17937    assert_eq!(
17938        *resolved_items.lock(),
17939        [&items[0..16], &items[items.len() - 4..items.len()]]
17940            .concat()
17941            .iter()
17942            .cloned()
17943            .map(|mut item| {
17944                if item.data.is_none() {
17945                    item.data = Some(default_data.clone());
17946                }
17947                item
17948            })
17949            .collect::<Vec<lsp::CompletionItem>>(),
17950        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
17951    );
17952    resolved_items.lock().clear();
17953
17954    cx.update_editor(|editor, window, cx| {
17955        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
17956    });
17957    cx.run_until_parked();
17958    // Completions that have already been resolved are skipped.
17959    assert_eq!(
17960        *resolved_items.lock(),
17961        items[items.len() - 17..items.len() - 4]
17962            .iter()
17963            .cloned()
17964            .map(|mut item| {
17965                if item.data.is_none() {
17966                    item.data = Some(default_data.clone());
17967                }
17968                item
17969            })
17970            .collect::<Vec<lsp::CompletionItem>>()
17971    );
17972    resolved_items.lock().clear();
17973}
17974
17975#[gpui::test]
17976async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
17977    init_test(cx, |_| {});
17978
17979    let mut cx = EditorLspTestContext::new(
17980        Language::new(
17981            LanguageConfig {
17982                matcher: LanguageMatcher {
17983                    path_suffixes: vec!["jsx".into()],
17984                    ..Default::default()
17985                },
17986                overrides: [(
17987                    "element".into(),
17988                    LanguageConfigOverride {
17989                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
17990                        ..Default::default()
17991                    },
17992                )]
17993                .into_iter()
17994                .collect(),
17995                ..Default::default()
17996            },
17997            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
17998        )
17999        .with_override_query("(jsx_self_closing_element) @element")
18000        .unwrap(),
18001        lsp::ServerCapabilities {
18002            completion_provider: Some(lsp::CompletionOptions {
18003                trigger_characters: Some(vec![":".to_string()]),
18004                ..Default::default()
18005            }),
18006            ..Default::default()
18007        },
18008        cx,
18009    )
18010    .await;
18011
18012    cx.lsp
18013        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
18014            Ok(Some(lsp::CompletionResponse::Array(vec![
18015                lsp::CompletionItem {
18016                    label: "bg-blue".into(),
18017                    ..Default::default()
18018                },
18019                lsp::CompletionItem {
18020                    label: "bg-red".into(),
18021                    ..Default::default()
18022                },
18023                lsp::CompletionItem {
18024                    label: "bg-yellow".into(),
18025                    ..Default::default()
18026                },
18027            ])))
18028        });
18029
18030    cx.set_state(r#"<p class="bgˇ" />"#);
18031
18032    // Trigger completion when typing a dash, because the dash is an extra
18033    // word character in the 'element' scope, which contains the cursor.
18034    cx.simulate_keystroke("-");
18035    cx.executor().run_until_parked();
18036    cx.update_editor(|editor, _, _| {
18037        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18038        {
18039            assert_eq!(
18040                completion_menu_entries(menu),
18041                &["bg-blue", "bg-red", "bg-yellow"]
18042            );
18043        } else {
18044            panic!("expected completion menu to be open");
18045        }
18046    });
18047
18048    cx.simulate_keystroke("l");
18049    cx.executor().run_until_parked();
18050    cx.update_editor(|editor, _, _| {
18051        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18052        {
18053            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
18054        } else {
18055            panic!("expected completion menu to be open");
18056        }
18057    });
18058
18059    // When filtering completions, consider the character after the '-' to
18060    // be the start of a subword.
18061    cx.set_state(r#"<p class="yelˇ" />"#);
18062    cx.simulate_keystroke("l");
18063    cx.executor().run_until_parked();
18064    cx.update_editor(|editor, _, _| {
18065        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18066        {
18067            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
18068        } else {
18069            panic!("expected completion menu to be open");
18070        }
18071    });
18072}
18073
18074fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
18075    let entries = menu.entries.borrow();
18076    entries.iter().map(|mat| mat.string.clone()).collect()
18077}
18078
18079#[gpui::test]
18080async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
18081    init_test(cx, |settings| {
18082        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
18083            Formatter::Prettier,
18084        )))
18085    });
18086
18087    let fs = FakeFs::new(cx.executor());
18088    fs.insert_file(path!("/file.ts"), Default::default()).await;
18089
18090    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
18091    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18092
18093    language_registry.add(Arc::new(Language::new(
18094        LanguageConfig {
18095            name: "TypeScript".into(),
18096            matcher: LanguageMatcher {
18097                path_suffixes: vec!["ts".to_string()],
18098                ..Default::default()
18099            },
18100            ..Default::default()
18101        },
18102        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
18103    )));
18104    update_test_language_settings(cx, |settings| {
18105        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
18106    });
18107
18108    let test_plugin = "test_plugin";
18109    let _ = language_registry.register_fake_lsp(
18110        "TypeScript",
18111        FakeLspAdapter {
18112            prettier_plugins: vec![test_plugin],
18113            ..Default::default()
18114        },
18115    );
18116
18117    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
18118    let buffer = project
18119        .update(cx, |project, cx| {
18120            project.open_local_buffer(path!("/file.ts"), cx)
18121        })
18122        .await
18123        .unwrap();
18124
18125    let buffer_text = "one\ntwo\nthree\n";
18126    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
18127    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
18128    editor.update_in(cx, |editor, window, cx| {
18129        editor.set_text(buffer_text, window, cx)
18130    });
18131
18132    editor
18133        .update_in(cx, |editor, window, cx| {
18134            editor.perform_format(
18135                project.clone(),
18136                FormatTrigger::Manual,
18137                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18138                window,
18139                cx,
18140            )
18141        })
18142        .unwrap()
18143        .await;
18144    assert_eq!(
18145        editor.update(cx, |editor, cx| editor.text(cx)),
18146        buffer_text.to_string() + prettier_format_suffix,
18147        "Test prettier formatting was not applied to the original buffer text",
18148    );
18149
18150    update_test_language_settings(cx, |settings| {
18151        settings.defaults.formatter = Some(SelectedFormatter::Auto)
18152    });
18153    let format = editor.update_in(cx, |editor, window, cx| {
18154        editor.perform_format(
18155            project.clone(),
18156            FormatTrigger::Manual,
18157            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18158            window,
18159            cx,
18160        )
18161    });
18162    format.await.unwrap();
18163    assert_eq!(
18164        editor.update(cx, |editor, cx| editor.text(cx)),
18165        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
18166        "Autoformatting (via test prettier) was not applied to the original buffer text",
18167    );
18168}
18169
18170#[gpui::test]
18171async fn test_addition_reverts(cx: &mut TestAppContext) {
18172    init_test(cx, |_| {});
18173    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18174    let base_text = indoc! {r#"
18175        struct Row;
18176        struct Row1;
18177        struct Row2;
18178
18179        struct Row4;
18180        struct Row5;
18181        struct Row6;
18182
18183        struct Row8;
18184        struct Row9;
18185        struct Row10;"#};
18186
18187    // When addition hunks are not adjacent to carets, no hunk revert is performed
18188    assert_hunk_revert(
18189        indoc! {r#"struct Row;
18190                   struct Row1;
18191                   struct Row1.1;
18192                   struct Row1.2;
18193                   struct Row2;ˇ
18194
18195                   struct Row4;
18196                   struct Row5;
18197                   struct Row6;
18198
18199                   struct Row8;
18200                   ˇstruct Row9;
18201                   struct Row9.1;
18202                   struct Row9.2;
18203                   struct Row9.3;
18204                   struct Row10;"#},
18205        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18206        indoc! {r#"struct Row;
18207                   struct Row1;
18208                   struct Row1.1;
18209                   struct Row1.2;
18210                   struct Row2;ˇ
18211
18212                   struct Row4;
18213                   struct Row5;
18214                   struct Row6;
18215
18216                   struct Row8;
18217                   ˇstruct Row9;
18218                   struct Row9.1;
18219                   struct Row9.2;
18220                   struct Row9.3;
18221                   struct Row10;"#},
18222        base_text,
18223        &mut cx,
18224    );
18225    // Same for selections
18226    assert_hunk_revert(
18227        indoc! {r#"struct Row;
18228                   struct Row1;
18229                   struct Row2;
18230                   struct Row2.1;
18231                   struct Row2.2;
18232                   «ˇ
18233                   struct Row4;
18234                   struct» Row5;
18235                   «struct Row6;
18236                   ˇ»
18237                   struct Row9.1;
18238                   struct Row9.2;
18239                   struct Row9.3;
18240                   struct Row8;
18241                   struct Row9;
18242                   struct Row10;"#},
18243        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18244        indoc! {r#"struct Row;
18245                   struct Row1;
18246                   struct Row2;
18247                   struct Row2.1;
18248                   struct Row2.2;
18249                   «ˇ
18250                   struct Row4;
18251                   struct» Row5;
18252                   «struct Row6;
18253                   ˇ»
18254                   struct Row9.1;
18255                   struct Row9.2;
18256                   struct Row9.3;
18257                   struct Row8;
18258                   struct Row9;
18259                   struct Row10;"#},
18260        base_text,
18261        &mut cx,
18262    );
18263
18264    // When carets and selections intersect the addition hunks, those are reverted.
18265    // Adjacent carets got merged.
18266    assert_hunk_revert(
18267        indoc! {r#"struct Row;
18268                   ˇ// something on the top
18269                   struct Row1;
18270                   struct Row2;
18271                   struct Roˇw3.1;
18272                   struct Row2.2;
18273                   struct Row2.3;ˇ
18274
18275                   struct Row4;
18276                   struct ˇRow5.1;
18277                   struct Row5.2;
18278                   struct «Rowˇ»5.3;
18279                   struct Row5;
18280                   struct Row6;
18281                   ˇ
18282                   struct Row9.1;
18283                   struct «Rowˇ»9.2;
18284                   struct «ˇRow»9.3;
18285                   struct Row8;
18286                   struct Row9;
18287                   «ˇ// something on bottom»
18288                   struct Row10;"#},
18289        vec![
18290            DiffHunkStatusKind::Added,
18291            DiffHunkStatusKind::Added,
18292            DiffHunkStatusKind::Added,
18293            DiffHunkStatusKind::Added,
18294            DiffHunkStatusKind::Added,
18295        ],
18296        indoc! {r#"struct Row;
18297                   ˇstruct Row1;
18298                   struct Row2;
18299                   ˇ
18300                   struct Row4;
18301                   ˇstruct Row5;
18302                   struct Row6;
18303                   ˇ
18304                   ˇstruct Row8;
18305                   struct Row9;
18306                   ˇstruct Row10;"#},
18307        base_text,
18308        &mut cx,
18309    );
18310}
18311
18312#[gpui::test]
18313async fn test_modification_reverts(cx: &mut TestAppContext) {
18314    init_test(cx, |_| {});
18315    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18316    let base_text = indoc! {r#"
18317        struct Row;
18318        struct Row1;
18319        struct Row2;
18320
18321        struct Row4;
18322        struct Row5;
18323        struct Row6;
18324
18325        struct Row8;
18326        struct Row9;
18327        struct Row10;"#};
18328
18329    // Modification hunks behave the same as the addition ones.
18330    assert_hunk_revert(
18331        indoc! {r#"struct Row;
18332                   struct Row1;
18333                   struct Row33;
18334                   ˇ
18335                   struct Row4;
18336                   struct Row5;
18337                   struct Row6;
18338                   ˇ
18339                   struct Row99;
18340                   struct Row9;
18341                   struct Row10;"#},
18342        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18343        indoc! {r#"struct Row;
18344                   struct Row1;
18345                   struct Row33;
18346                   ˇ
18347                   struct Row4;
18348                   struct Row5;
18349                   struct Row6;
18350                   ˇ
18351                   struct Row99;
18352                   struct Row9;
18353                   struct Row10;"#},
18354        base_text,
18355        &mut cx,
18356    );
18357    assert_hunk_revert(
18358        indoc! {r#"struct Row;
18359                   struct Row1;
18360                   struct Row33;
18361                   «ˇ
18362                   struct Row4;
18363                   struct» Row5;
18364                   «struct Row6;
18365                   ˇ»
18366                   struct Row99;
18367                   struct Row9;
18368                   struct Row10;"#},
18369        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18370        indoc! {r#"struct Row;
18371                   struct Row1;
18372                   struct Row33;
18373                   «ˇ
18374                   struct Row4;
18375                   struct» Row5;
18376                   «struct Row6;
18377                   ˇ»
18378                   struct Row99;
18379                   struct Row9;
18380                   struct Row10;"#},
18381        base_text,
18382        &mut cx,
18383    );
18384
18385    assert_hunk_revert(
18386        indoc! {r#"ˇstruct Row1.1;
18387                   struct Row1;
18388                   «ˇstr»uct Row22;
18389
18390                   struct ˇRow44;
18391                   struct Row5;
18392                   struct «Rˇ»ow66;ˇ
18393
18394                   «struˇ»ct Row88;
18395                   struct Row9;
18396                   struct Row1011;ˇ"#},
18397        vec![
18398            DiffHunkStatusKind::Modified,
18399            DiffHunkStatusKind::Modified,
18400            DiffHunkStatusKind::Modified,
18401            DiffHunkStatusKind::Modified,
18402            DiffHunkStatusKind::Modified,
18403            DiffHunkStatusKind::Modified,
18404        ],
18405        indoc! {r#"struct Row;
18406                   ˇstruct Row1;
18407                   struct Row2;
18408                   ˇ
18409                   struct Row4;
18410                   ˇstruct Row5;
18411                   struct Row6;
18412                   ˇ
18413                   struct Row8;
18414                   ˇstruct Row9;
18415                   struct Row10;ˇ"#},
18416        base_text,
18417        &mut cx,
18418    );
18419}
18420
18421#[gpui::test]
18422async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
18423    init_test(cx, |_| {});
18424    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18425    let base_text = indoc! {r#"
18426        one
18427
18428        two
18429        three
18430        "#};
18431
18432    cx.set_head_text(base_text);
18433    cx.set_state("\nˇ\n");
18434    cx.executor().run_until_parked();
18435    cx.update_editor(|editor, _window, cx| {
18436        editor.expand_selected_diff_hunks(cx);
18437    });
18438    cx.executor().run_until_parked();
18439    cx.update_editor(|editor, window, cx| {
18440        editor.backspace(&Default::default(), window, cx);
18441    });
18442    cx.run_until_parked();
18443    cx.assert_state_with_diff(
18444        indoc! {r#"
18445
18446        - two
18447        - threeˇ
18448        +
18449        "#}
18450        .to_string(),
18451    );
18452}
18453
18454#[gpui::test]
18455async fn test_deletion_reverts(cx: &mut TestAppContext) {
18456    init_test(cx, |_| {});
18457    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18458    let base_text = indoc! {r#"struct Row;
18459struct Row1;
18460struct Row2;
18461
18462struct Row4;
18463struct Row5;
18464struct Row6;
18465
18466struct Row8;
18467struct Row9;
18468struct Row10;"#};
18469
18470    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
18471    assert_hunk_revert(
18472        indoc! {r#"struct Row;
18473                   struct Row2;
18474
18475                   ˇstruct Row4;
18476                   struct Row5;
18477                   struct Row6;
18478                   ˇ
18479                   struct Row8;
18480                   struct Row10;"#},
18481        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18482        indoc! {r#"struct Row;
18483                   struct Row2;
18484
18485                   ˇstruct Row4;
18486                   struct Row5;
18487                   struct Row6;
18488                   ˇ
18489                   struct Row8;
18490                   struct Row10;"#},
18491        base_text,
18492        &mut cx,
18493    );
18494    assert_hunk_revert(
18495        indoc! {r#"struct Row;
18496                   struct Row2;
18497
18498                   «ˇstruct Row4;
18499                   struct» Row5;
18500                   «struct Row6;
18501                   ˇ»
18502                   struct Row8;
18503                   struct Row10;"#},
18504        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18505        indoc! {r#"struct Row;
18506                   struct Row2;
18507
18508                   «ˇstruct Row4;
18509                   struct» Row5;
18510                   «struct Row6;
18511                   ˇ»
18512                   struct Row8;
18513                   struct Row10;"#},
18514        base_text,
18515        &mut cx,
18516    );
18517
18518    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
18519    assert_hunk_revert(
18520        indoc! {r#"struct Row;
18521                   ˇstruct Row2;
18522
18523                   struct Row4;
18524                   struct Row5;
18525                   struct Row6;
18526
18527                   struct Row8;ˇ
18528                   struct Row10;"#},
18529        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18530        indoc! {r#"struct Row;
18531                   struct Row1;
18532                   ˇstruct Row2;
18533
18534                   struct Row4;
18535                   struct Row5;
18536                   struct Row6;
18537
18538                   struct Row8;ˇ
18539                   struct Row9;
18540                   struct Row10;"#},
18541        base_text,
18542        &mut cx,
18543    );
18544    assert_hunk_revert(
18545        indoc! {r#"struct Row;
18546                   struct Row2«ˇ;
18547                   struct Row4;
18548                   struct» Row5;
18549                   «struct Row6;
18550
18551                   struct Row8;ˇ»
18552                   struct Row10;"#},
18553        vec![
18554            DiffHunkStatusKind::Deleted,
18555            DiffHunkStatusKind::Deleted,
18556            DiffHunkStatusKind::Deleted,
18557        ],
18558        indoc! {r#"struct Row;
18559                   struct Row1;
18560                   struct Row2«ˇ;
18561
18562                   struct Row4;
18563                   struct» Row5;
18564                   «struct Row6;
18565
18566                   struct Row8;ˇ»
18567                   struct Row9;
18568                   struct Row10;"#},
18569        base_text,
18570        &mut cx,
18571    );
18572}
18573
18574#[gpui::test]
18575async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
18576    init_test(cx, |_| {});
18577
18578    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
18579    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
18580    let base_text_3 =
18581        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
18582
18583    let text_1 = edit_first_char_of_every_line(base_text_1);
18584    let text_2 = edit_first_char_of_every_line(base_text_2);
18585    let text_3 = edit_first_char_of_every_line(base_text_3);
18586
18587    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
18588    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
18589    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
18590
18591    let multibuffer = cx.new(|cx| {
18592        let mut multibuffer = MultiBuffer::new(ReadWrite);
18593        multibuffer.push_excerpts(
18594            buffer_1.clone(),
18595            [
18596                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18597                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18598                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18599            ],
18600            cx,
18601        );
18602        multibuffer.push_excerpts(
18603            buffer_2.clone(),
18604            [
18605                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18606                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18607                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18608            ],
18609            cx,
18610        );
18611        multibuffer.push_excerpts(
18612            buffer_3.clone(),
18613            [
18614                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18615                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18616                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18617            ],
18618            cx,
18619        );
18620        multibuffer
18621    });
18622
18623    let fs = FakeFs::new(cx.executor());
18624    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
18625    let (editor, cx) = cx
18626        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
18627    editor.update_in(cx, |editor, _window, cx| {
18628        for (buffer, diff_base) in [
18629            (buffer_1.clone(), base_text_1),
18630            (buffer_2.clone(), base_text_2),
18631            (buffer_3.clone(), base_text_3),
18632        ] {
18633            let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
18634            editor
18635                .buffer
18636                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
18637        }
18638    });
18639    cx.executor().run_until_parked();
18640
18641    editor.update_in(cx, |editor, window, cx| {
18642        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}");
18643        editor.select_all(&SelectAll, window, cx);
18644        editor.git_restore(&Default::default(), window, cx);
18645    });
18646    cx.executor().run_until_parked();
18647
18648    // When all ranges are selected, all buffer hunks are reverted.
18649    editor.update(cx, |editor, cx| {
18650        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");
18651    });
18652    buffer_1.update(cx, |buffer, _| {
18653        assert_eq!(buffer.text(), base_text_1);
18654    });
18655    buffer_2.update(cx, |buffer, _| {
18656        assert_eq!(buffer.text(), base_text_2);
18657    });
18658    buffer_3.update(cx, |buffer, _| {
18659        assert_eq!(buffer.text(), base_text_3);
18660    });
18661
18662    editor.update_in(cx, |editor, window, cx| {
18663        editor.undo(&Default::default(), window, cx);
18664    });
18665
18666    editor.update_in(cx, |editor, window, cx| {
18667        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18668            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
18669        });
18670        editor.git_restore(&Default::default(), window, cx);
18671    });
18672
18673    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
18674    // but not affect buffer_2 and its related excerpts.
18675    editor.update(cx, |editor, cx| {
18676        assert_eq!(
18677            editor.text(cx),
18678            "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}"
18679        );
18680    });
18681    buffer_1.update(cx, |buffer, _| {
18682        assert_eq!(buffer.text(), base_text_1);
18683    });
18684    buffer_2.update(cx, |buffer, _| {
18685        assert_eq!(
18686            buffer.text(),
18687            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
18688        );
18689    });
18690    buffer_3.update(cx, |buffer, _| {
18691        assert_eq!(
18692            buffer.text(),
18693            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
18694        );
18695    });
18696
18697    fn edit_first_char_of_every_line(text: &str) -> String {
18698        text.split('\n')
18699            .map(|line| format!("X{}", &line[1..]))
18700            .collect::<Vec<_>>()
18701            .join("\n")
18702    }
18703}
18704
18705#[gpui::test]
18706async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
18707    init_test(cx, |_| {});
18708
18709    let cols = 4;
18710    let rows = 10;
18711    let sample_text_1 = sample_text(rows, cols, 'a');
18712    assert_eq!(
18713        sample_text_1,
18714        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
18715    );
18716    let sample_text_2 = sample_text(rows, cols, 'l');
18717    assert_eq!(
18718        sample_text_2,
18719        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
18720    );
18721    let sample_text_3 = sample_text(rows, cols, 'v');
18722    assert_eq!(
18723        sample_text_3,
18724        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
18725    );
18726
18727    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
18728    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
18729    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
18730
18731    let multi_buffer = cx.new(|cx| {
18732        let mut multibuffer = MultiBuffer::new(ReadWrite);
18733        multibuffer.push_excerpts(
18734            buffer_1.clone(),
18735            [
18736                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18737                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18738                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18739            ],
18740            cx,
18741        );
18742        multibuffer.push_excerpts(
18743            buffer_2.clone(),
18744            [
18745                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18746                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18747                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18748            ],
18749            cx,
18750        );
18751        multibuffer.push_excerpts(
18752            buffer_3.clone(),
18753            [
18754                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18755                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18756                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18757            ],
18758            cx,
18759        );
18760        multibuffer
18761    });
18762
18763    let fs = FakeFs::new(cx.executor());
18764    fs.insert_tree(
18765        "/a",
18766        json!({
18767            "main.rs": sample_text_1,
18768            "other.rs": sample_text_2,
18769            "lib.rs": sample_text_3,
18770        }),
18771    )
18772    .await;
18773    let project = Project::test(fs, ["/a".as_ref()], cx).await;
18774    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18775    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18776    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
18777        Editor::new(
18778            EditorMode::full(),
18779            multi_buffer,
18780            Some(project.clone()),
18781            window,
18782            cx,
18783        )
18784    });
18785    let multibuffer_item_id = workspace
18786        .update(cx, |workspace, window, cx| {
18787            assert!(
18788                workspace.active_item(cx).is_none(),
18789                "active item should be None before the first item is added"
18790            );
18791            workspace.add_item_to_active_pane(
18792                Box::new(multi_buffer_editor.clone()),
18793                None,
18794                true,
18795                window,
18796                cx,
18797            );
18798            let active_item = workspace
18799                .active_item(cx)
18800                .expect("should have an active item after adding the multi buffer");
18801            assert_eq!(
18802                active_item.buffer_kind(cx),
18803                ItemBufferKind::Multibuffer,
18804                "A multi buffer was expected to active after adding"
18805            );
18806            active_item.item_id()
18807        })
18808        .unwrap();
18809    cx.executor().run_until_parked();
18810
18811    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18812        editor.change_selections(
18813            SelectionEffects::scroll(Autoscroll::Next),
18814            window,
18815            cx,
18816            |s| s.select_ranges(Some(1..2)),
18817        );
18818        editor.open_excerpts(&OpenExcerpts, window, cx);
18819    });
18820    cx.executor().run_until_parked();
18821    let first_item_id = workspace
18822        .update(cx, |workspace, window, cx| {
18823            let active_item = workspace
18824                .active_item(cx)
18825                .expect("should have an active item after navigating into the 1st buffer");
18826            let first_item_id = active_item.item_id();
18827            assert_ne!(
18828                first_item_id, multibuffer_item_id,
18829                "Should navigate into the 1st buffer and activate it"
18830            );
18831            assert_eq!(
18832                active_item.buffer_kind(cx),
18833                ItemBufferKind::Singleton,
18834                "New active item should be a singleton buffer"
18835            );
18836            assert_eq!(
18837                active_item
18838                    .act_as::<Editor>(cx)
18839                    .expect("should have navigated into an editor for the 1st buffer")
18840                    .read(cx)
18841                    .text(cx),
18842                sample_text_1
18843            );
18844
18845            workspace
18846                .go_back(workspace.active_pane().downgrade(), window, cx)
18847                .detach_and_log_err(cx);
18848
18849            first_item_id
18850        })
18851        .unwrap();
18852    cx.executor().run_until_parked();
18853    workspace
18854        .update(cx, |workspace, _, cx| {
18855            let active_item = workspace
18856                .active_item(cx)
18857                .expect("should have an active item after navigating back");
18858            assert_eq!(
18859                active_item.item_id(),
18860                multibuffer_item_id,
18861                "Should navigate back to the multi buffer"
18862            );
18863            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
18864        })
18865        .unwrap();
18866
18867    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18868        editor.change_selections(
18869            SelectionEffects::scroll(Autoscroll::Next),
18870            window,
18871            cx,
18872            |s| s.select_ranges(Some(39..40)),
18873        );
18874        editor.open_excerpts(&OpenExcerpts, window, cx);
18875    });
18876    cx.executor().run_until_parked();
18877    let second_item_id = workspace
18878        .update(cx, |workspace, window, cx| {
18879            let active_item = workspace
18880                .active_item(cx)
18881                .expect("should have an active item after navigating into the 2nd buffer");
18882            let second_item_id = active_item.item_id();
18883            assert_ne!(
18884                second_item_id, multibuffer_item_id,
18885                "Should navigate away from the multibuffer"
18886            );
18887            assert_ne!(
18888                second_item_id, first_item_id,
18889                "Should navigate into the 2nd buffer and activate it"
18890            );
18891            assert_eq!(
18892                active_item.buffer_kind(cx),
18893                ItemBufferKind::Singleton,
18894                "New active item should be a singleton buffer"
18895            );
18896            assert_eq!(
18897                active_item
18898                    .act_as::<Editor>(cx)
18899                    .expect("should have navigated into an editor")
18900                    .read(cx)
18901                    .text(cx),
18902                sample_text_2
18903            );
18904
18905            workspace
18906                .go_back(workspace.active_pane().downgrade(), window, cx)
18907                .detach_and_log_err(cx);
18908
18909            second_item_id
18910        })
18911        .unwrap();
18912    cx.executor().run_until_parked();
18913    workspace
18914        .update(cx, |workspace, _, cx| {
18915            let active_item = workspace
18916                .active_item(cx)
18917                .expect("should have an active item after navigating back from the 2nd buffer");
18918            assert_eq!(
18919                active_item.item_id(),
18920                multibuffer_item_id,
18921                "Should navigate back from the 2nd buffer to the multi buffer"
18922            );
18923            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
18924        })
18925        .unwrap();
18926
18927    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18928        editor.change_selections(
18929            SelectionEffects::scroll(Autoscroll::Next),
18930            window,
18931            cx,
18932            |s| s.select_ranges(Some(70..70)),
18933        );
18934        editor.open_excerpts(&OpenExcerpts, window, cx);
18935    });
18936    cx.executor().run_until_parked();
18937    workspace
18938        .update(cx, |workspace, window, cx| {
18939            let active_item = workspace
18940                .active_item(cx)
18941                .expect("should have an active item after navigating into the 3rd buffer");
18942            let third_item_id = active_item.item_id();
18943            assert_ne!(
18944                third_item_id, multibuffer_item_id,
18945                "Should navigate into the 3rd buffer and activate it"
18946            );
18947            assert_ne!(third_item_id, first_item_id);
18948            assert_ne!(third_item_id, second_item_id);
18949            assert_eq!(
18950                active_item.buffer_kind(cx),
18951                ItemBufferKind::Singleton,
18952                "New active item should be a singleton buffer"
18953            );
18954            assert_eq!(
18955                active_item
18956                    .act_as::<Editor>(cx)
18957                    .expect("should have navigated into an editor")
18958                    .read(cx)
18959                    .text(cx),
18960                sample_text_3
18961            );
18962
18963            workspace
18964                .go_back(workspace.active_pane().downgrade(), window, cx)
18965                .detach_and_log_err(cx);
18966        })
18967        .unwrap();
18968    cx.executor().run_until_parked();
18969    workspace
18970        .update(cx, |workspace, _, cx| {
18971            let active_item = workspace
18972                .active_item(cx)
18973                .expect("should have an active item after navigating back from the 3rd buffer");
18974            assert_eq!(
18975                active_item.item_id(),
18976                multibuffer_item_id,
18977                "Should navigate back from the 3rd buffer to the multi buffer"
18978            );
18979            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
18980        })
18981        .unwrap();
18982}
18983
18984#[gpui::test]
18985async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
18986    init_test(cx, |_| {});
18987
18988    let mut cx = EditorTestContext::new(cx).await;
18989
18990    let diff_base = r#"
18991        use some::mod;
18992
18993        const A: u32 = 42;
18994
18995        fn main() {
18996            println!("hello");
18997
18998            println!("world");
18999        }
19000        "#
19001    .unindent();
19002
19003    cx.set_state(
19004        &r#"
19005        use some::modified;
19006
19007        ˇ
19008        fn main() {
19009            println!("hello there");
19010
19011            println!("around the");
19012            println!("world");
19013        }
19014        "#
19015        .unindent(),
19016    );
19017
19018    cx.set_head_text(&diff_base);
19019    executor.run_until_parked();
19020
19021    cx.update_editor(|editor, window, cx| {
19022        editor.go_to_next_hunk(&GoToHunk, window, cx);
19023        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19024    });
19025    executor.run_until_parked();
19026    cx.assert_state_with_diff(
19027        r#"
19028          use some::modified;
19029
19030
19031          fn main() {
19032        -     println!("hello");
19033        + ˇ    println!("hello there");
19034
19035              println!("around the");
19036              println!("world");
19037          }
19038        "#
19039        .unindent(),
19040    );
19041
19042    cx.update_editor(|editor, window, cx| {
19043        for _ in 0..2 {
19044            editor.go_to_next_hunk(&GoToHunk, window, cx);
19045            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19046        }
19047    });
19048    executor.run_until_parked();
19049    cx.assert_state_with_diff(
19050        r#"
19051        - use some::mod;
19052        + ˇuse some::modified;
19053
19054
19055          fn main() {
19056        -     println!("hello");
19057        +     println!("hello there");
19058
19059        +     println!("around the");
19060              println!("world");
19061          }
19062        "#
19063        .unindent(),
19064    );
19065
19066    cx.update_editor(|editor, window, cx| {
19067        editor.go_to_next_hunk(&GoToHunk, window, cx);
19068        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19069    });
19070    executor.run_until_parked();
19071    cx.assert_state_with_diff(
19072        r#"
19073        - use some::mod;
19074        + use some::modified;
19075
19076        - const A: u32 = 42;
19077          ˇ
19078          fn main() {
19079        -     println!("hello");
19080        +     println!("hello there");
19081
19082        +     println!("around the");
19083              println!("world");
19084          }
19085        "#
19086        .unindent(),
19087    );
19088
19089    cx.update_editor(|editor, window, cx| {
19090        editor.cancel(&Cancel, window, cx);
19091    });
19092
19093    cx.assert_state_with_diff(
19094        r#"
19095          use some::modified;
19096
19097          ˇ
19098          fn main() {
19099              println!("hello there");
19100
19101              println!("around the");
19102              println!("world");
19103          }
19104        "#
19105        .unindent(),
19106    );
19107}
19108
19109#[gpui::test]
19110async fn test_diff_base_change_with_expanded_diff_hunks(
19111    executor: BackgroundExecutor,
19112    cx: &mut TestAppContext,
19113) {
19114    init_test(cx, |_| {});
19115
19116    let mut cx = EditorTestContext::new(cx).await;
19117
19118    let diff_base = r#"
19119        use some::mod1;
19120        use some::mod2;
19121
19122        const A: u32 = 42;
19123        const B: u32 = 42;
19124        const C: u32 = 42;
19125
19126        fn main() {
19127            println!("hello");
19128
19129            println!("world");
19130        }
19131        "#
19132    .unindent();
19133
19134    cx.set_state(
19135        &r#"
19136        use some::mod2;
19137
19138        const A: u32 = 42;
19139        const C: u32 = 42;
19140
19141        fn main(ˇ) {
19142            //println!("hello");
19143
19144            println!("world");
19145            //
19146            //
19147        }
19148        "#
19149        .unindent(),
19150    );
19151
19152    cx.set_head_text(&diff_base);
19153    executor.run_until_parked();
19154
19155    cx.update_editor(|editor, window, cx| {
19156        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19157    });
19158    executor.run_until_parked();
19159    cx.assert_state_with_diff(
19160        r#"
19161        - use some::mod1;
19162          use some::mod2;
19163
19164          const A: u32 = 42;
19165        - const B: u32 = 42;
19166          const C: u32 = 42;
19167
19168          fn main(ˇ) {
19169        -     println!("hello");
19170        +     //println!("hello");
19171
19172              println!("world");
19173        +     //
19174        +     //
19175          }
19176        "#
19177        .unindent(),
19178    );
19179
19180    cx.set_head_text("new diff base!");
19181    executor.run_until_parked();
19182    cx.assert_state_with_diff(
19183        r#"
19184        - new diff base!
19185        + use some::mod2;
19186        +
19187        + const A: u32 = 42;
19188        + const C: u32 = 42;
19189        +
19190        + fn main(ˇ) {
19191        +     //println!("hello");
19192        +
19193        +     println!("world");
19194        +     //
19195        +     //
19196        + }
19197        "#
19198        .unindent(),
19199    );
19200}
19201
19202#[gpui::test]
19203async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
19204    init_test(cx, |_| {});
19205
19206    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19207    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19208    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19209    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19210    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
19211    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
19212
19213    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
19214    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
19215    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
19216
19217    let multi_buffer = cx.new(|cx| {
19218        let mut multibuffer = MultiBuffer::new(ReadWrite);
19219        multibuffer.push_excerpts(
19220            buffer_1.clone(),
19221            [
19222                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19223                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19224                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19225            ],
19226            cx,
19227        );
19228        multibuffer.push_excerpts(
19229            buffer_2.clone(),
19230            [
19231                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19232                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19233                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19234            ],
19235            cx,
19236        );
19237        multibuffer.push_excerpts(
19238            buffer_3.clone(),
19239            [
19240                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19241                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19242                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19243            ],
19244            cx,
19245        );
19246        multibuffer
19247    });
19248
19249    let editor =
19250        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19251    editor
19252        .update(cx, |editor, _window, cx| {
19253            for (buffer, diff_base) in [
19254                (buffer_1.clone(), file_1_old),
19255                (buffer_2.clone(), file_2_old),
19256                (buffer_3.clone(), file_3_old),
19257            ] {
19258                let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
19259                editor
19260                    .buffer
19261                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
19262            }
19263        })
19264        .unwrap();
19265
19266    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19267    cx.run_until_parked();
19268
19269    cx.assert_editor_state(
19270        &"
19271            ˇaaa
19272            ccc
19273            ddd
19274
19275            ggg
19276            hhh
19277
19278
19279            lll
19280            mmm
19281            NNN
19282
19283            qqq
19284            rrr
19285
19286            uuu
19287            111
19288            222
19289            333
19290
19291            666
19292            777
19293
19294            000
19295            !!!"
19296        .unindent(),
19297    );
19298
19299    cx.update_editor(|editor, window, cx| {
19300        editor.select_all(&SelectAll, window, cx);
19301        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19302    });
19303    cx.executor().run_until_parked();
19304
19305    cx.assert_state_with_diff(
19306        "
19307            «aaa
19308          - bbb
19309            ccc
19310            ddd
19311
19312            ggg
19313            hhh
19314
19315
19316            lll
19317            mmm
19318          - nnn
19319          + NNN
19320
19321            qqq
19322            rrr
19323
19324            uuu
19325            111
19326            222
19327            333
19328
19329          + 666
19330            777
19331
19332            000
19333            !!!ˇ»"
19334            .unindent(),
19335    );
19336}
19337
19338#[gpui::test]
19339async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
19340    init_test(cx, |_| {});
19341
19342    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
19343    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
19344
19345    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
19346    let multi_buffer = cx.new(|cx| {
19347        let mut multibuffer = MultiBuffer::new(ReadWrite);
19348        multibuffer.push_excerpts(
19349            buffer.clone(),
19350            [
19351                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
19352                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
19353                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
19354            ],
19355            cx,
19356        );
19357        multibuffer
19358    });
19359
19360    let editor =
19361        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19362    editor
19363        .update(cx, |editor, _window, cx| {
19364            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
19365            editor
19366                .buffer
19367                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
19368        })
19369        .unwrap();
19370
19371    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19372    cx.run_until_parked();
19373
19374    cx.update_editor(|editor, window, cx| {
19375        editor.expand_all_diff_hunks(&Default::default(), window, cx)
19376    });
19377    cx.executor().run_until_parked();
19378
19379    // When the start of a hunk coincides with the start of its excerpt,
19380    // the hunk is expanded. When the start of a hunk is earlier than
19381    // the start of its excerpt, the hunk is not expanded.
19382    cx.assert_state_with_diff(
19383        "
19384            ˇaaa
19385          - bbb
19386          + BBB
19387
19388          - ddd
19389          - eee
19390          + DDD
19391          + EEE
19392            fff
19393
19394            iii
19395        "
19396        .unindent(),
19397    );
19398}
19399
19400#[gpui::test]
19401async fn test_edits_around_expanded_insertion_hunks(
19402    executor: BackgroundExecutor,
19403    cx: &mut TestAppContext,
19404) {
19405    init_test(cx, |_| {});
19406
19407    let mut cx = EditorTestContext::new(cx).await;
19408
19409    let diff_base = r#"
19410        use some::mod1;
19411        use some::mod2;
19412
19413        const A: u32 = 42;
19414
19415        fn main() {
19416            println!("hello");
19417
19418            println!("world");
19419        }
19420        "#
19421    .unindent();
19422    executor.run_until_parked();
19423    cx.set_state(
19424        &r#"
19425        use some::mod1;
19426        use some::mod2;
19427
19428        const A: u32 = 42;
19429        const B: u32 = 42;
19430        const C: u32 = 42;
19431        ˇ
19432
19433        fn main() {
19434            println!("hello");
19435
19436            println!("world");
19437        }
19438        "#
19439        .unindent(),
19440    );
19441
19442    cx.set_head_text(&diff_base);
19443    executor.run_until_parked();
19444
19445    cx.update_editor(|editor, window, cx| {
19446        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19447    });
19448    executor.run_until_parked();
19449
19450    cx.assert_state_with_diff(
19451        r#"
19452        use some::mod1;
19453        use some::mod2;
19454
19455        const A: u32 = 42;
19456      + const B: u32 = 42;
19457      + const C: u32 = 42;
19458      + ˇ
19459
19460        fn main() {
19461            println!("hello");
19462
19463            println!("world");
19464        }
19465      "#
19466        .unindent(),
19467    );
19468
19469    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
19470    executor.run_until_parked();
19471
19472    cx.assert_state_with_diff(
19473        r#"
19474        use some::mod1;
19475        use some::mod2;
19476
19477        const A: u32 = 42;
19478      + const B: u32 = 42;
19479      + const C: u32 = 42;
19480      + const D: u32 = 42;
19481      + ˇ
19482
19483        fn main() {
19484            println!("hello");
19485
19486            println!("world");
19487        }
19488      "#
19489        .unindent(),
19490    );
19491
19492    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
19493    executor.run_until_parked();
19494
19495    cx.assert_state_with_diff(
19496        r#"
19497        use some::mod1;
19498        use some::mod2;
19499
19500        const A: u32 = 42;
19501      + const B: u32 = 42;
19502      + const C: u32 = 42;
19503      + const D: u32 = 42;
19504      + const E: u32 = 42;
19505      + ˇ
19506
19507        fn main() {
19508            println!("hello");
19509
19510            println!("world");
19511        }
19512      "#
19513        .unindent(),
19514    );
19515
19516    cx.update_editor(|editor, window, cx| {
19517        editor.delete_line(&DeleteLine, window, cx);
19518    });
19519    executor.run_until_parked();
19520
19521    cx.assert_state_with_diff(
19522        r#"
19523        use some::mod1;
19524        use some::mod2;
19525
19526        const A: u32 = 42;
19527      + const B: u32 = 42;
19528      + const C: u32 = 42;
19529      + const D: u32 = 42;
19530      + const E: u32 = 42;
19531        ˇ
19532        fn main() {
19533            println!("hello");
19534
19535            println!("world");
19536        }
19537      "#
19538        .unindent(),
19539    );
19540
19541    cx.update_editor(|editor, window, cx| {
19542        editor.move_up(&MoveUp, window, cx);
19543        editor.delete_line(&DeleteLine, window, cx);
19544        editor.move_up(&MoveUp, window, cx);
19545        editor.delete_line(&DeleteLine, window, cx);
19546        editor.move_up(&MoveUp, window, cx);
19547        editor.delete_line(&DeleteLine, window, cx);
19548    });
19549    executor.run_until_parked();
19550    cx.assert_state_with_diff(
19551        r#"
19552        use some::mod1;
19553        use some::mod2;
19554
19555        const A: u32 = 42;
19556      + const B: u32 = 42;
19557        ˇ
19558        fn main() {
19559            println!("hello");
19560
19561            println!("world");
19562        }
19563      "#
19564        .unindent(),
19565    );
19566
19567    cx.update_editor(|editor, window, cx| {
19568        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
19569        editor.delete_line(&DeleteLine, window, cx);
19570    });
19571    executor.run_until_parked();
19572    cx.assert_state_with_diff(
19573        r#"
19574        ˇ
19575        fn main() {
19576            println!("hello");
19577
19578            println!("world");
19579        }
19580      "#
19581        .unindent(),
19582    );
19583}
19584
19585#[gpui::test]
19586async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
19587    init_test(cx, |_| {});
19588
19589    let mut cx = EditorTestContext::new(cx).await;
19590    cx.set_head_text(indoc! { "
19591        one
19592        two
19593        three
19594        four
19595        five
19596        "
19597    });
19598    cx.set_state(indoc! { "
19599        one
19600        ˇthree
19601        five
19602    "});
19603    cx.run_until_parked();
19604    cx.update_editor(|editor, window, cx| {
19605        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19606    });
19607    cx.assert_state_with_diff(
19608        indoc! { "
19609        one
19610      - two
19611        ˇthree
19612      - four
19613        five
19614    "}
19615        .to_string(),
19616    );
19617    cx.update_editor(|editor, window, cx| {
19618        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19619    });
19620
19621    cx.assert_state_with_diff(
19622        indoc! { "
19623        one
19624        ˇthree
19625        five
19626    "}
19627        .to_string(),
19628    );
19629
19630    cx.set_state(indoc! { "
19631        one
19632        ˇTWO
19633        three
19634        four
19635        five
19636    "});
19637    cx.run_until_parked();
19638    cx.update_editor(|editor, window, cx| {
19639        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19640    });
19641
19642    cx.assert_state_with_diff(
19643        indoc! { "
19644            one
19645          - two
19646          + ˇTWO
19647            three
19648            four
19649            five
19650        "}
19651        .to_string(),
19652    );
19653    cx.update_editor(|editor, window, cx| {
19654        editor.move_up(&Default::default(), window, cx);
19655        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19656    });
19657    cx.assert_state_with_diff(
19658        indoc! { "
19659            one
19660            ˇTWO
19661            three
19662            four
19663            five
19664        "}
19665        .to_string(),
19666    );
19667}
19668
19669#[gpui::test]
19670async fn test_edits_around_expanded_deletion_hunks(
19671    executor: BackgroundExecutor,
19672    cx: &mut TestAppContext,
19673) {
19674    init_test(cx, |_| {});
19675
19676    let mut cx = EditorTestContext::new(cx).await;
19677
19678    let diff_base = r#"
19679        use some::mod1;
19680        use some::mod2;
19681
19682        const A: u32 = 42;
19683        const B: u32 = 42;
19684        const C: u32 = 42;
19685
19686
19687        fn main() {
19688            println!("hello");
19689
19690            println!("world");
19691        }
19692    "#
19693    .unindent();
19694    executor.run_until_parked();
19695    cx.set_state(
19696        &r#"
19697        use some::mod1;
19698        use some::mod2;
19699
19700        ˇconst B: u32 = 42;
19701        const C: u32 = 42;
19702
19703
19704        fn main() {
19705            println!("hello");
19706
19707            println!("world");
19708        }
19709        "#
19710        .unindent(),
19711    );
19712
19713    cx.set_head_text(&diff_base);
19714    executor.run_until_parked();
19715
19716    cx.update_editor(|editor, window, cx| {
19717        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19718    });
19719    executor.run_until_parked();
19720
19721    cx.assert_state_with_diff(
19722        r#"
19723        use some::mod1;
19724        use some::mod2;
19725
19726      - const A: u32 = 42;
19727        ˇconst B: u32 = 42;
19728        const C: u32 = 42;
19729
19730
19731        fn main() {
19732            println!("hello");
19733
19734            println!("world");
19735        }
19736      "#
19737        .unindent(),
19738    );
19739
19740    cx.update_editor(|editor, window, cx| {
19741        editor.delete_line(&DeleteLine, window, cx);
19742    });
19743    executor.run_until_parked();
19744    cx.assert_state_with_diff(
19745        r#"
19746        use some::mod1;
19747        use some::mod2;
19748
19749      - const A: u32 = 42;
19750      - const B: u32 = 42;
19751        ˇconst C: u32 = 42;
19752
19753
19754        fn main() {
19755            println!("hello");
19756
19757            println!("world");
19758        }
19759      "#
19760        .unindent(),
19761    );
19762
19763    cx.update_editor(|editor, window, cx| {
19764        editor.delete_line(&DeleteLine, window, cx);
19765    });
19766    executor.run_until_parked();
19767    cx.assert_state_with_diff(
19768        r#"
19769        use some::mod1;
19770        use some::mod2;
19771
19772      - const A: u32 = 42;
19773      - const B: u32 = 42;
19774      - const C: u32 = 42;
19775        ˇ
19776
19777        fn main() {
19778            println!("hello");
19779
19780            println!("world");
19781        }
19782      "#
19783        .unindent(),
19784    );
19785
19786    cx.update_editor(|editor, window, cx| {
19787        editor.handle_input("replacement", window, cx);
19788    });
19789    executor.run_until_parked();
19790    cx.assert_state_with_diff(
19791        r#"
19792        use some::mod1;
19793        use some::mod2;
19794
19795      - const A: u32 = 42;
19796      - const B: u32 = 42;
19797      - const C: u32 = 42;
19798      -
19799      + replacementˇ
19800
19801        fn main() {
19802            println!("hello");
19803
19804            println!("world");
19805        }
19806      "#
19807        .unindent(),
19808    );
19809}
19810
19811#[gpui::test]
19812async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19813    init_test(cx, |_| {});
19814
19815    let mut cx = EditorTestContext::new(cx).await;
19816
19817    let base_text = r#"
19818        one
19819        two
19820        three
19821        four
19822        five
19823    "#
19824    .unindent();
19825    executor.run_until_parked();
19826    cx.set_state(
19827        &r#"
19828        one
19829        two
19830        fˇour
19831        five
19832        "#
19833        .unindent(),
19834    );
19835
19836    cx.set_head_text(&base_text);
19837    executor.run_until_parked();
19838
19839    cx.update_editor(|editor, window, cx| {
19840        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19841    });
19842    executor.run_until_parked();
19843
19844    cx.assert_state_with_diff(
19845        r#"
19846          one
19847          two
19848        - three
19849          fˇour
19850          five
19851        "#
19852        .unindent(),
19853    );
19854
19855    cx.update_editor(|editor, window, cx| {
19856        editor.backspace(&Backspace, window, cx);
19857        editor.backspace(&Backspace, window, cx);
19858    });
19859    executor.run_until_parked();
19860    cx.assert_state_with_diff(
19861        r#"
19862          one
19863          two
19864        - threeˇ
19865        - four
19866        + our
19867          five
19868        "#
19869        .unindent(),
19870    );
19871}
19872
19873#[gpui::test]
19874async fn test_edit_after_expanded_modification_hunk(
19875    executor: BackgroundExecutor,
19876    cx: &mut TestAppContext,
19877) {
19878    init_test(cx, |_| {});
19879
19880    let mut cx = EditorTestContext::new(cx).await;
19881
19882    let diff_base = r#"
19883        use some::mod1;
19884        use some::mod2;
19885
19886        const A: u32 = 42;
19887        const B: u32 = 42;
19888        const C: u32 = 42;
19889        const D: u32 = 42;
19890
19891
19892        fn main() {
19893            println!("hello");
19894
19895            println!("world");
19896        }"#
19897    .unindent();
19898
19899    cx.set_state(
19900        &r#"
19901        use some::mod1;
19902        use some::mod2;
19903
19904        const A: u32 = 42;
19905        const B: u32 = 42;
19906        const C: u32 = 43ˇ
19907        const D: u32 = 42;
19908
19909
19910        fn main() {
19911            println!("hello");
19912
19913            println!("world");
19914        }"#
19915        .unindent(),
19916    );
19917
19918    cx.set_head_text(&diff_base);
19919    executor.run_until_parked();
19920    cx.update_editor(|editor, window, cx| {
19921        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19922    });
19923    executor.run_until_parked();
19924
19925    cx.assert_state_with_diff(
19926        r#"
19927        use some::mod1;
19928        use some::mod2;
19929
19930        const A: u32 = 42;
19931        const B: u32 = 42;
19932      - const C: u32 = 42;
19933      + const C: u32 = 43ˇ
19934        const D: u32 = 42;
19935
19936
19937        fn main() {
19938            println!("hello");
19939
19940            println!("world");
19941        }"#
19942        .unindent(),
19943    );
19944
19945    cx.update_editor(|editor, window, cx| {
19946        editor.handle_input("\nnew_line\n", window, cx);
19947    });
19948    executor.run_until_parked();
19949
19950    cx.assert_state_with_diff(
19951        r#"
19952        use some::mod1;
19953        use some::mod2;
19954
19955        const A: u32 = 42;
19956        const B: u32 = 42;
19957      - const C: u32 = 42;
19958      + const C: u32 = 43
19959      + new_line
19960      + ˇ
19961        const D: u32 = 42;
19962
19963
19964        fn main() {
19965            println!("hello");
19966
19967            println!("world");
19968        }"#
19969        .unindent(),
19970    );
19971}
19972
19973#[gpui::test]
19974async fn test_stage_and_unstage_added_file_hunk(
19975    executor: BackgroundExecutor,
19976    cx: &mut TestAppContext,
19977) {
19978    init_test(cx, |_| {});
19979
19980    let mut cx = EditorTestContext::new(cx).await;
19981    cx.update_editor(|editor, _, cx| {
19982        editor.set_expand_all_diff_hunks(cx);
19983    });
19984
19985    let working_copy = r#"
19986            ˇfn main() {
19987                println!("hello, world!");
19988            }
19989        "#
19990    .unindent();
19991
19992    cx.set_state(&working_copy);
19993    executor.run_until_parked();
19994
19995    cx.assert_state_with_diff(
19996        r#"
19997            + ˇfn main() {
19998            +     println!("hello, world!");
19999            + }
20000        "#
20001        .unindent(),
20002    );
20003    cx.assert_index_text(None);
20004
20005    cx.update_editor(|editor, window, cx| {
20006        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20007    });
20008    executor.run_until_parked();
20009    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
20010    cx.assert_state_with_diff(
20011        r#"
20012            + ˇfn main() {
20013            +     println!("hello, world!");
20014            + }
20015        "#
20016        .unindent(),
20017    );
20018
20019    cx.update_editor(|editor, window, cx| {
20020        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20021    });
20022    executor.run_until_parked();
20023    cx.assert_index_text(None);
20024}
20025
20026async fn setup_indent_guides_editor(
20027    text: &str,
20028    cx: &mut TestAppContext,
20029) -> (BufferId, EditorTestContext) {
20030    init_test(cx, |_| {});
20031
20032    let mut cx = EditorTestContext::new(cx).await;
20033
20034    let buffer_id = cx.update_editor(|editor, window, cx| {
20035        editor.set_text(text, window, cx);
20036        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
20037
20038        buffer_ids[0]
20039    });
20040
20041    (buffer_id, cx)
20042}
20043
20044fn assert_indent_guides(
20045    range: Range<u32>,
20046    expected: Vec<IndentGuide>,
20047    active_indices: Option<Vec<usize>>,
20048    cx: &mut EditorTestContext,
20049) {
20050    let indent_guides = cx.update_editor(|editor, window, cx| {
20051        let snapshot = editor.snapshot(window, cx).display_snapshot;
20052        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
20053            editor,
20054            MultiBufferRow(range.start)..MultiBufferRow(range.end),
20055            true,
20056            &snapshot,
20057            cx,
20058        );
20059
20060        indent_guides.sort_by(|a, b| {
20061            a.depth.cmp(&b.depth).then(
20062                a.start_row
20063                    .cmp(&b.start_row)
20064                    .then(a.end_row.cmp(&b.end_row)),
20065            )
20066        });
20067        indent_guides
20068    });
20069
20070    if let Some(expected) = active_indices {
20071        let active_indices = cx.update_editor(|editor, window, cx| {
20072            let snapshot = editor.snapshot(window, cx).display_snapshot;
20073            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
20074        });
20075
20076        assert_eq!(
20077            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
20078            expected,
20079            "Active indent guide indices do not match"
20080        );
20081    }
20082
20083    assert_eq!(indent_guides, expected, "Indent guides do not match");
20084}
20085
20086fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
20087    IndentGuide {
20088        buffer_id,
20089        start_row: MultiBufferRow(start_row),
20090        end_row: MultiBufferRow(end_row),
20091        depth,
20092        tab_size: 4,
20093        settings: IndentGuideSettings {
20094            enabled: true,
20095            line_width: 1,
20096            active_line_width: 1,
20097            coloring: IndentGuideColoring::default(),
20098            background_coloring: IndentGuideBackgroundColoring::default(),
20099        },
20100    }
20101}
20102
20103#[gpui::test]
20104async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
20105    let (buffer_id, mut cx) = setup_indent_guides_editor(
20106        &"
20107        fn main() {
20108            let a = 1;
20109        }"
20110        .unindent(),
20111        cx,
20112    )
20113    .await;
20114
20115    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20116}
20117
20118#[gpui::test]
20119async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
20120    let (buffer_id, mut cx) = setup_indent_guides_editor(
20121        &"
20122        fn main() {
20123            let a = 1;
20124            let b = 2;
20125        }"
20126        .unindent(),
20127        cx,
20128    )
20129    .await;
20130
20131    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
20132}
20133
20134#[gpui::test]
20135async fn test_indent_guide_nested(cx: &mut TestAppContext) {
20136    let (buffer_id, mut cx) = setup_indent_guides_editor(
20137        &"
20138        fn main() {
20139            let a = 1;
20140            if a == 3 {
20141                let b = 2;
20142            } else {
20143                let c = 3;
20144            }
20145        }"
20146        .unindent(),
20147        cx,
20148    )
20149    .await;
20150
20151    assert_indent_guides(
20152        0..8,
20153        vec![
20154            indent_guide(buffer_id, 1, 6, 0),
20155            indent_guide(buffer_id, 3, 3, 1),
20156            indent_guide(buffer_id, 5, 5, 1),
20157        ],
20158        None,
20159        &mut cx,
20160    );
20161}
20162
20163#[gpui::test]
20164async fn test_indent_guide_tab(cx: &mut TestAppContext) {
20165    let (buffer_id, mut cx) = setup_indent_guides_editor(
20166        &"
20167        fn main() {
20168            let a = 1;
20169                let b = 2;
20170            let c = 3;
20171        }"
20172        .unindent(),
20173        cx,
20174    )
20175    .await;
20176
20177    assert_indent_guides(
20178        0..5,
20179        vec![
20180            indent_guide(buffer_id, 1, 3, 0),
20181            indent_guide(buffer_id, 2, 2, 1),
20182        ],
20183        None,
20184        &mut cx,
20185    );
20186}
20187
20188#[gpui::test]
20189async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
20190    let (buffer_id, mut cx) = setup_indent_guides_editor(
20191        &"
20192        fn main() {
20193            let a = 1;
20194
20195            let c = 3;
20196        }"
20197        .unindent(),
20198        cx,
20199    )
20200    .await;
20201
20202    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
20203}
20204
20205#[gpui::test]
20206async fn test_indent_guide_complex(cx: &mut TestAppContext) {
20207    let (buffer_id, mut cx) = setup_indent_guides_editor(
20208        &"
20209        fn main() {
20210            let a = 1;
20211
20212            let c = 3;
20213
20214            if a == 3 {
20215                let b = 2;
20216            } else {
20217                let c = 3;
20218            }
20219        }"
20220        .unindent(),
20221        cx,
20222    )
20223    .await;
20224
20225    assert_indent_guides(
20226        0..11,
20227        vec![
20228            indent_guide(buffer_id, 1, 9, 0),
20229            indent_guide(buffer_id, 6, 6, 1),
20230            indent_guide(buffer_id, 8, 8, 1),
20231        ],
20232        None,
20233        &mut cx,
20234    );
20235}
20236
20237#[gpui::test]
20238async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
20239    let (buffer_id, mut cx) = setup_indent_guides_editor(
20240        &"
20241        fn main() {
20242            let a = 1;
20243
20244            let c = 3;
20245
20246            if a == 3 {
20247                let b = 2;
20248            } else {
20249                let c = 3;
20250            }
20251        }"
20252        .unindent(),
20253        cx,
20254    )
20255    .await;
20256
20257    assert_indent_guides(
20258        1..11,
20259        vec![
20260            indent_guide(buffer_id, 1, 9, 0),
20261            indent_guide(buffer_id, 6, 6, 1),
20262            indent_guide(buffer_id, 8, 8, 1),
20263        ],
20264        None,
20265        &mut cx,
20266    );
20267}
20268
20269#[gpui::test]
20270async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
20271    let (buffer_id, mut cx) = setup_indent_guides_editor(
20272        &"
20273        fn main() {
20274            let a = 1;
20275
20276            let c = 3;
20277
20278            if a == 3 {
20279                let b = 2;
20280            } else {
20281                let c = 3;
20282            }
20283        }"
20284        .unindent(),
20285        cx,
20286    )
20287    .await;
20288
20289    assert_indent_guides(
20290        1..10,
20291        vec![
20292            indent_guide(buffer_id, 1, 9, 0),
20293            indent_guide(buffer_id, 6, 6, 1),
20294            indent_guide(buffer_id, 8, 8, 1),
20295        ],
20296        None,
20297        &mut cx,
20298    );
20299}
20300
20301#[gpui::test]
20302async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
20303    let (buffer_id, mut cx) = setup_indent_guides_editor(
20304        &"
20305        fn main() {
20306            if a {
20307                b(
20308                    c,
20309                    d,
20310                )
20311            } else {
20312                e(
20313                    f
20314                )
20315            }
20316        }"
20317        .unindent(),
20318        cx,
20319    )
20320    .await;
20321
20322    assert_indent_guides(
20323        0..11,
20324        vec![
20325            indent_guide(buffer_id, 1, 10, 0),
20326            indent_guide(buffer_id, 2, 5, 1),
20327            indent_guide(buffer_id, 7, 9, 1),
20328            indent_guide(buffer_id, 3, 4, 2),
20329            indent_guide(buffer_id, 8, 8, 2),
20330        ],
20331        None,
20332        &mut cx,
20333    );
20334
20335    cx.update_editor(|editor, window, cx| {
20336        editor.fold_at(MultiBufferRow(2), window, cx);
20337        assert_eq!(
20338            editor.display_text(cx),
20339            "
20340            fn main() {
20341                if a {
20342                    b(⋯
20343                    )
20344                } else {
20345                    e(
20346                        f
20347                    )
20348                }
20349            }"
20350            .unindent()
20351        );
20352    });
20353
20354    assert_indent_guides(
20355        0..11,
20356        vec![
20357            indent_guide(buffer_id, 1, 10, 0),
20358            indent_guide(buffer_id, 2, 5, 1),
20359            indent_guide(buffer_id, 7, 9, 1),
20360            indent_guide(buffer_id, 8, 8, 2),
20361        ],
20362        None,
20363        &mut cx,
20364    );
20365}
20366
20367#[gpui::test]
20368async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
20369    let (buffer_id, mut cx) = setup_indent_guides_editor(
20370        &"
20371        block1
20372            block2
20373                block3
20374                    block4
20375            block2
20376        block1
20377        block1"
20378            .unindent(),
20379        cx,
20380    )
20381    .await;
20382
20383    assert_indent_guides(
20384        1..10,
20385        vec![
20386            indent_guide(buffer_id, 1, 4, 0),
20387            indent_guide(buffer_id, 2, 3, 1),
20388            indent_guide(buffer_id, 3, 3, 2),
20389        ],
20390        None,
20391        &mut cx,
20392    );
20393}
20394
20395#[gpui::test]
20396async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
20397    let (buffer_id, mut cx) = setup_indent_guides_editor(
20398        &"
20399        block1
20400            block2
20401                block3
20402
20403        block1
20404        block1"
20405            .unindent(),
20406        cx,
20407    )
20408    .await;
20409
20410    assert_indent_guides(
20411        0..6,
20412        vec![
20413            indent_guide(buffer_id, 1, 2, 0),
20414            indent_guide(buffer_id, 2, 2, 1),
20415        ],
20416        None,
20417        &mut cx,
20418    );
20419}
20420
20421#[gpui::test]
20422async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
20423    let (buffer_id, mut cx) = setup_indent_guides_editor(
20424        &"
20425        function component() {
20426        \treturn (
20427        \t\t\t
20428        \t\t<div>
20429        \t\t\t<abc></abc>
20430        \t\t</div>
20431        \t)
20432        }"
20433        .unindent(),
20434        cx,
20435    )
20436    .await;
20437
20438    assert_indent_guides(
20439        0..8,
20440        vec![
20441            indent_guide(buffer_id, 1, 6, 0),
20442            indent_guide(buffer_id, 2, 5, 1),
20443            indent_guide(buffer_id, 4, 4, 2),
20444        ],
20445        None,
20446        &mut cx,
20447    );
20448}
20449
20450#[gpui::test]
20451async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
20452    let (buffer_id, mut cx) = setup_indent_guides_editor(
20453        &"
20454        function component() {
20455        \treturn (
20456        \t
20457        \t\t<div>
20458        \t\t\t<abc></abc>
20459        \t\t</div>
20460        \t)
20461        }"
20462        .unindent(),
20463        cx,
20464    )
20465    .await;
20466
20467    assert_indent_guides(
20468        0..8,
20469        vec![
20470            indent_guide(buffer_id, 1, 6, 0),
20471            indent_guide(buffer_id, 2, 5, 1),
20472            indent_guide(buffer_id, 4, 4, 2),
20473        ],
20474        None,
20475        &mut cx,
20476    );
20477}
20478
20479#[gpui::test]
20480async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
20481    let (buffer_id, mut cx) = setup_indent_guides_editor(
20482        &"
20483        block1
20484
20485
20486
20487            block2
20488        "
20489        .unindent(),
20490        cx,
20491    )
20492    .await;
20493
20494    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20495}
20496
20497#[gpui::test]
20498async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
20499    let (buffer_id, mut cx) = setup_indent_guides_editor(
20500        &"
20501        def a:
20502        \tb = 3
20503        \tif True:
20504        \t\tc = 4
20505        \t\td = 5
20506        \tprint(b)
20507        "
20508        .unindent(),
20509        cx,
20510    )
20511    .await;
20512
20513    assert_indent_guides(
20514        0..6,
20515        vec![
20516            indent_guide(buffer_id, 1, 5, 0),
20517            indent_guide(buffer_id, 3, 4, 1),
20518        ],
20519        None,
20520        &mut cx,
20521    );
20522}
20523
20524#[gpui::test]
20525async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
20526    let (buffer_id, mut cx) = setup_indent_guides_editor(
20527        &"
20528    fn main() {
20529        let a = 1;
20530    }"
20531        .unindent(),
20532        cx,
20533    )
20534    .await;
20535
20536    cx.update_editor(|editor, window, cx| {
20537        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20538            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20539        });
20540    });
20541
20542    assert_indent_guides(
20543        0..3,
20544        vec![indent_guide(buffer_id, 1, 1, 0)],
20545        Some(vec![0]),
20546        &mut cx,
20547    );
20548}
20549
20550#[gpui::test]
20551async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
20552    let (buffer_id, mut cx) = setup_indent_guides_editor(
20553        &"
20554    fn main() {
20555        if 1 == 2 {
20556            let a = 1;
20557        }
20558    }"
20559        .unindent(),
20560        cx,
20561    )
20562    .await;
20563
20564    cx.update_editor(|editor, window, cx| {
20565        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20566            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20567        });
20568    });
20569
20570    assert_indent_guides(
20571        0..4,
20572        vec![
20573            indent_guide(buffer_id, 1, 3, 0),
20574            indent_guide(buffer_id, 2, 2, 1),
20575        ],
20576        Some(vec![1]),
20577        &mut cx,
20578    );
20579
20580    cx.update_editor(|editor, window, cx| {
20581        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20582            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20583        });
20584    });
20585
20586    assert_indent_guides(
20587        0..4,
20588        vec![
20589            indent_guide(buffer_id, 1, 3, 0),
20590            indent_guide(buffer_id, 2, 2, 1),
20591        ],
20592        Some(vec![1]),
20593        &mut cx,
20594    );
20595
20596    cx.update_editor(|editor, window, cx| {
20597        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20598            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
20599        });
20600    });
20601
20602    assert_indent_guides(
20603        0..4,
20604        vec![
20605            indent_guide(buffer_id, 1, 3, 0),
20606            indent_guide(buffer_id, 2, 2, 1),
20607        ],
20608        Some(vec![0]),
20609        &mut cx,
20610    );
20611}
20612
20613#[gpui::test]
20614async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
20615    let (buffer_id, mut cx) = setup_indent_guides_editor(
20616        &"
20617    fn main() {
20618        let a = 1;
20619
20620        let b = 2;
20621    }"
20622        .unindent(),
20623        cx,
20624    )
20625    .await;
20626
20627    cx.update_editor(|editor, window, cx| {
20628        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20629            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20630        });
20631    });
20632
20633    assert_indent_guides(
20634        0..5,
20635        vec![indent_guide(buffer_id, 1, 3, 0)],
20636        Some(vec![0]),
20637        &mut cx,
20638    );
20639}
20640
20641#[gpui::test]
20642async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
20643    let (buffer_id, mut cx) = setup_indent_guides_editor(
20644        &"
20645    def m:
20646        a = 1
20647        pass"
20648            .unindent(),
20649        cx,
20650    )
20651    .await;
20652
20653    cx.update_editor(|editor, window, cx| {
20654        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20655            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20656        });
20657    });
20658
20659    assert_indent_guides(
20660        0..3,
20661        vec![indent_guide(buffer_id, 1, 2, 0)],
20662        Some(vec![0]),
20663        &mut cx,
20664    );
20665}
20666
20667#[gpui::test]
20668async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
20669    init_test(cx, |_| {});
20670    let mut cx = EditorTestContext::new(cx).await;
20671    let text = indoc! {
20672        "
20673        impl A {
20674            fn b() {
20675                0;
20676                3;
20677                5;
20678                6;
20679                7;
20680            }
20681        }
20682        "
20683    };
20684    let base_text = indoc! {
20685        "
20686        impl A {
20687            fn b() {
20688                0;
20689                1;
20690                2;
20691                3;
20692                4;
20693            }
20694            fn c() {
20695                5;
20696                6;
20697                7;
20698            }
20699        }
20700        "
20701    };
20702
20703    cx.update_editor(|editor, window, cx| {
20704        editor.set_text(text, window, cx);
20705
20706        editor.buffer().update(cx, |multibuffer, cx| {
20707            let buffer = multibuffer.as_singleton().unwrap();
20708            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
20709
20710            multibuffer.set_all_diff_hunks_expanded(cx);
20711            multibuffer.add_diff(diff, cx);
20712
20713            buffer.read(cx).remote_id()
20714        })
20715    });
20716    cx.run_until_parked();
20717
20718    cx.assert_state_with_diff(
20719        indoc! { "
20720          impl A {
20721              fn b() {
20722                  0;
20723        -         1;
20724        -         2;
20725                  3;
20726        -         4;
20727        -     }
20728        -     fn c() {
20729                  5;
20730                  6;
20731                  7;
20732              }
20733          }
20734          ˇ"
20735        }
20736        .to_string(),
20737    );
20738
20739    let mut actual_guides = cx.update_editor(|editor, window, cx| {
20740        editor
20741            .snapshot(window, cx)
20742            .buffer_snapshot()
20743            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
20744            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
20745            .collect::<Vec<_>>()
20746    });
20747    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
20748    assert_eq!(
20749        actual_guides,
20750        vec![
20751            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
20752            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
20753            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
20754        ]
20755    );
20756}
20757
20758#[gpui::test]
20759async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
20760    init_test(cx, |_| {});
20761    let mut cx = EditorTestContext::new(cx).await;
20762
20763    let diff_base = r#"
20764        a
20765        b
20766        c
20767        "#
20768    .unindent();
20769
20770    cx.set_state(
20771        &r#"
20772        ˇA
20773        b
20774        C
20775        "#
20776        .unindent(),
20777    );
20778    cx.set_head_text(&diff_base);
20779    cx.update_editor(|editor, window, cx| {
20780        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20781    });
20782    executor.run_until_parked();
20783
20784    let both_hunks_expanded = r#"
20785        - a
20786        + ˇA
20787          b
20788        - c
20789        + C
20790        "#
20791    .unindent();
20792
20793    cx.assert_state_with_diff(both_hunks_expanded.clone());
20794
20795    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20796        let snapshot = editor.snapshot(window, cx);
20797        let hunks = editor
20798            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
20799            .collect::<Vec<_>>();
20800        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20801        let buffer_id = hunks[0].buffer_id;
20802        hunks
20803            .into_iter()
20804            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20805            .collect::<Vec<_>>()
20806    });
20807    assert_eq!(hunk_ranges.len(), 2);
20808
20809    cx.update_editor(|editor, _, cx| {
20810        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20811    });
20812    executor.run_until_parked();
20813
20814    let second_hunk_expanded = r#"
20815          ˇA
20816          b
20817        - c
20818        + C
20819        "#
20820    .unindent();
20821
20822    cx.assert_state_with_diff(second_hunk_expanded);
20823
20824    cx.update_editor(|editor, _, cx| {
20825        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20826    });
20827    executor.run_until_parked();
20828
20829    cx.assert_state_with_diff(both_hunks_expanded.clone());
20830
20831    cx.update_editor(|editor, _, cx| {
20832        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20833    });
20834    executor.run_until_parked();
20835
20836    let first_hunk_expanded = r#"
20837        - a
20838        + ˇA
20839          b
20840          C
20841        "#
20842    .unindent();
20843
20844    cx.assert_state_with_diff(first_hunk_expanded);
20845
20846    cx.update_editor(|editor, _, cx| {
20847        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20848    });
20849    executor.run_until_parked();
20850
20851    cx.assert_state_with_diff(both_hunks_expanded);
20852
20853    cx.set_state(
20854        &r#"
20855        ˇA
20856        b
20857        "#
20858        .unindent(),
20859    );
20860    cx.run_until_parked();
20861
20862    // TODO this cursor position seems bad
20863    cx.assert_state_with_diff(
20864        r#"
20865        - ˇa
20866        + A
20867          b
20868        "#
20869        .unindent(),
20870    );
20871
20872    cx.update_editor(|editor, window, cx| {
20873        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20874    });
20875
20876    cx.assert_state_with_diff(
20877        r#"
20878            - ˇa
20879            + A
20880              b
20881            - c
20882            "#
20883        .unindent(),
20884    );
20885
20886    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20887        let snapshot = editor.snapshot(window, cx);
20888        let hunks = editor
20889            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
20890            .collect::<Vec<_>>();
20891        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20892        let buffer_id = hunks[0].buffer_id;
20893        hunks
20894            .into_iter()
20895            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20896            .collect::<Vec<_>>()
20897    });
20898    assert_eq!(hunk_ranges.len(), 2);
20899
20900    cx.update_editor(|editor, _, cx| {
20901        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20902    });
20903    executor.run_until_parked();
20904
20905    cx.assert_state_with_diff(
20906        r#"
20907        - ˇa
20908        + A
20909          b
20910        "#
20911        .unindent(),
20912    );
20913}
20914
20915#[gpui::test]
20916async fn test_toggle_deletion_hunk_at_start_of_file(
20917    executor: BackgroundExecutor,
20918    cx: &mut TestAppContext,
20919) {
20920    init_test(cx, |_| {});
20921    let mut cx = EditorTestContext::new(cx).await;
20922
20923    let diff_base = r#"
20924        a
20925        b
20926        c
20927        "#
20928    .unindent();
20929
20930    cx.set_state(
20931        &r#"
20932        ˇb
20933        c
20934        "#
20935        .unindent(),
20936    );
20937    cx.set_head_text(&diff_base);
20938    cx.update_editor(|editor, window, cx| {
20939        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20940    });
20941    executor.run_until_parked();
20942
20943    let hunk_expanded = r#"
20944        - a
20945          ˇb
20946          c
20947        "#
20948    .unindent();
20949
20950    cx.assert_state_with_diff(hunk_expanded.clone());
20951
20952    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20953        let snapshot = editor.snapshot(window, cx);
20954        let hunks = editor
20955            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
20956            .collect::<Vec<_>>();
20957        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20958        let buffer_id = hunks[0].buffer_id;
20959        hunks
20960            .into_iter()
20961            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20962            .collect::<Vec<_>>()
20963    });
20964    assert_eq!(hunk_ranges.len(), 1);
20965
20966    cx.update_editor(|editor, _, cx| {
20967        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20968    });
20969    executor.run_until_parked();
20970
20971    let hunk_collapsed = r#"
20972          ˇb
20973          c
20974        "#
20975    .unindent();
20976
20977    cx.assert_state_with_diff(hunk_collapsed);
20978
20979    cx.update_editor(|editor, _, cx| {
20980        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20981    });
20982    executor.run_until_parked();
20983
20984    cx.assert_state_with_diff(hunk_expanded);
20985}
20986
20987#[gpui::test]
20988async fn test_display_diff_hunks(cx: &mut TestAppContext) {
20989    init_test(cx, |_| {});
20990
20991    let fs = FakeFs::new(cx.executor());
20992    fs.insert_tree(
20993        path!("/test"),
20994        json!({
20995            ".git": {},
20996            "file-1": "ONE\n",
20997            "file-2": "TWO\n",
20998            "file-3": "THREE\n",
20999        }),
21000    )
21001    .await;
21002
21003    fs.set_head_for_repo(
21004        path!("/test/.git").as_ref(),
21005        &[
21006            ("file-1", "one\n".into()),
21007            ("file-2", "two\n".into()),
21008            ("file-3", "three\n".into()),
21009        ],
21010        "deadbeef",
21011    );
21012
21013    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
21014    let mut buffers = vec![];
21015    for i in 1..=3 {
21016        let buffer = project
21017            .update(cx, |project, cx| {
21018                let path = format!(path!("/test/file-{}"), i);
21019                project.open_local_buffer(path, cx)
21020            })
21021            .await
21022            .unwrap();
21023        buffers.push(buffer);
21024    }
21025
21026    let multibuffer = cx.new(|cx| {
21027        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
21028        multibuffer.set_all_diff_hunks_expanded(cx);
21029        for buffer in &buffers {
21030            let snapshot = buffer.read(cx).snapshot();
21031            multibuffer.set_excerpts_for_path(
21032                PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
21033                buffer.clone(),
21034                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
21035                2,
21036                cx,
21037            );
21038        }
21039        multibuffer
21040    });
21041
21042    let editor = cx.add_window(|window, cx| {
21043        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
21044    });
21045    cx.run_until_parked();
21046
21047    let snapshot = editor
21048        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21049        .unwrap();
21050    let hunks = snapshot
21051        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
21052        .map(|hunk| match hunk {
21053            DisplayDiffHunk::Unfolded {
21054                display_row_range, ..
21055            } => display_row_range,
21056            DisplayDiffHunk::Folded { .. } => unreachable!(),
21057        })
21058        .collect::<Vec<_>>();
21059    assert_eq!(
21060        hunks,
21061        [
21062            DisplayRow(2)..DisplayRow(4),
21063            DisplayRow(7)..DisplayRow(9),
21064            DisplayRow(12)..DisplayRow(14),
21065        ]
21066    );
21067}
21068
21069#[gpui::test]
21070async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
21071    init_test(cx, |_| {});
21072
21073    let mut cx = EditorTestContext::new(cx).await;
21074    cx.set_head_text(indoc! { "
21075        one
21076        two
21077        three
21078        four
21079        five
21080        "
21081    });
21082    cx.set_index_text(indoc! { "
21083        one
21084        two
21085        three
21086        four
21087        five
21088        "
21089    });
21090    cx.set_state(indoc! {"
21091        one
21092        TWO
21093        ˇTHREE
21094        FOUR
21095        five
21096    "});
21097    cx.run_until_parked();
21098    cx.update_editor(|editor, window, cx| {
21099        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21100    });
21101    cx.run_until_parked();
21102    cx.assert_index_text(Some(indoc! {"
21103        one
21104        TWO
21105        THREE
21106        FOUR
21107        five
21108    "}));
21109    cx.set_state(indoc! { "
21110        one
21111        TWO
21112        ˇTHREE-HUNDRED
21113        FOUR
21114        five
21115    "});
21116    cx.run_until_parked();
21117    cx.update_editor(|editor, window, cx| {
21118        let snapshot = editor.snapshot(window, cx);
21119        let hunks = editor
21120            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21121            .collect::<Vec<_>>();
21122        assert_eq!(hunks.len(), 1);
21123        assert_eq!(
21124            hunks[0].status(),
21125            DiffHunkStatus {
21126                kind: DiffHunkStatusKind::Modified,
21127                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
21128            }
21129        );
21130
21131        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21132    });
21133    cx.run_until_parked();
21134    cx.assert_index_text(Some(indoc! {"
21135        one
21136        TWO
21137        THREE-HUNDRED
21138        FOUR
21139        five
21140    "}));
21141}
21142
21143#[gpui::test]
21144fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
21145    init_test(cx, |_| {});
21146
21147    let editor = cx.add_window(|window, cx| {
21148        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
21149        build_editor(buffer, window, cx)
21150    });
21151
21152    let render_args = Arc::new(Mutex::new(None));
21153    let snapshot = editor
21154        .update(cx, |editor, window, cx| {
21155            let snapshot = editor.buffer().read(cx).snapshot(cx);
21156            let range =
21157                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
21158
21159            struct RenderArgs {
21160                row: MultiBufferRow,
21161                folded: bool,
21162                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
21163            }
21164
21165            let crease = Crease::inline(
21166                range,
21167                FoldPlaceholder::test(),
21168                {
21169                    let toggle_callback = render_args.clone();
21170                    move |row, folded, callback, _window, _cx| {
21171                        *toggle_callback.lock() = Some(RenderArgs {
21172                            row,
21173                            folded,
21174                            callback,
21175                        });
21176                        div()
21177                    }
21178                },
21179                |_row, _folded, _window, _cx| div(),
21180            );
21181
21182            editor.insert_creases(Some(crease), cx);
21183            let snapshot = editor.snapshot(window, cx);
21184            let _div =
21185                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
21186            snapshot
21187        })
21188        .unwrap();
21189
21190    let render_args = render_args.lock().take().unwrap();
21191    assert_eq!(render_args.row, MultiBufferRow(1));
21192    assert!(!render_args.folded);
21193    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21194
21195    cx.update_window(*editor, |_, window, cx| {
21196        (render_args.callback)(true, window, cx)
21197    })
21198    .unwrap();
21199    let snapshot = editor
21200        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21201        .unwrap();
21202    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
21203
21204    cx.update_window(*editor, |_, window, cx| {
21205        (render_args.callback)(false, window, cx)
21206    })
21207    .unwrap();
21208    let snapshot = editor
21209        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21210        .unwrap();
21211    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21212}
21213
21214#[gpui::test]
21215async fn test_input_text(cx: &mut TestAppContext) {
21216    init_test(cx, |_| {});
21217    let mut cx = EditorTestContext::new(cx).await;
21218
21219    cx.set_state(
21220        &r#"ˇone
21221        two
21222
21223        three
21224        fourˇ
21225        five
21226
21227        siˇx"#
21228            .unindent(),
21229    );
21230
21231    cx.dispatch_action(HandleInput(String::new()));
21232    cx.assert_editor_state(
21233        &r#"ˇone
21234        two
21235
21236        three
21237        fourˇ
21238        five
21239
21240        siˇx"#
21241            .unindent(),
21242    );
21243
21244    cx.dispatch_action(HandleInput("AAAA".to_string()));
21245    cx.assert_editor_state(
21246        &r#"AAAAˇone
21247        two
21248
21249        three
21250        fourAAAAˇ
21251        five
21252
21253        siAAAAˇx"#
21254            .unindent(),
21255    );
21256}
21257
21258#[gpui::test]
21259async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
21260    init_test(cx, |_| {});
21261
21262    let mut cx = EditorTestContext::new(cx).await;
21263    cx.set_state(
21264        r#"let foo = 1;
21265let foo = 2;
21266let foo = 3;
21267let fooˇ = 4;
21268let foo = 5;
21269let foo = 6;
21270let foo = 7;
21271let foo = 8;
21272let foo = 9;
21273let foo = 10;
21274let foo = 11;
21275let foo = 12;
21276let foo = 13;
21277let foo = 14;
21278let foo = 15;"#,
21279    );
21280
21281    cx.update_editor(|e, window, cx| {
21282        assert_eq!(
21283            e.next_scroll_position,
21284            NextScrollCursorCenterTopBottom::Center,
21285            "Default next scroll direction is center",
21286        );
21287
21288        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21289        assert_eq!(
21290            e.next_scroll_position,
21291            NextScrollCursorCenterTopBottom::Top,
21292            "After center, next scroll direction should be top",
21293        );
21294
21295        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21296        assert_eq!(
21297            e.next_scroll_position,
21298            NextScrollCursorCenterTopBottom::Bottom,
21299            "After top, next scroll direction should be bottom",
21300        );
21301
21302        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21303        assert_eq!(
21304            e.next_scroll_position,
21305            NextScrollCursorCenterTopBottom::Center,
21306            "After bottom, scrolling should start over",
21307        );
21308
21309        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21310        assert_eq!(
21311            e.next_scroll_position,
21312            NextScrollCursorCenterTopBottom::Top,
21313            "Scrolling continues if retriggered fast enough"
21314        );
21315    });
21316
21317    cx.executor()
21318        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
21319    cx.executor().run_until_parked();
21320    cx.update_editor(|e, _, _| {
21321        assert_eq!(
21322            e.next_scroll_position,
21323            NextScrollCursorCenterTopBottom::Center,
21324            "If scrolling is not triggered fast enough, it should reset"
21325        );
21326    });
21327}
21328
21329#[gpui::test]
21330async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
21331    init_test(cx, |_| {});
21332    let mut cx = EditorLspTestContext::new_rust(
21333        lsp::ServerCapabilities {
21334            definition_provider: Some(lsp::OneOf::Left(true)),
21335            references_provider: Some(lsp::OneOf::Left(true)),
21336            ..lsp::ServerCapabilities::default()
21337        },
21338        cx,
21339    )
21340    .await;
21341
21342    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
21343        let go_to_definition = cx
21344            .lsp
21345            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21346                move |params, _| async move {
21347                    if empty_go_to_definition {
21348                        Ok(None)
21349                    } else {
21350                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
21351                            uri: params.text_document_position_params.text_document.uri,
21352                            range: lsp::Range::new(
21353                                lsp::Position::new(4, 3),
21354                                lsp::Position::new(4, 6),
21355                            ),
21356                        })))
21357                    }
21358                },
21359            );
21360        let references = cx
21361            .lsp
21362            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21363                Ok(Some(vec![lsp::Location {
21364                    uri: params.text_document_position.text_document.uri,
21365                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
21366                }]))
21367            });
21368        (go_to_definition, references)
21369    };
21370
21371    cx.set_state(
21372        &r#"fn one() {
21373            let mut a = ˇtwo();
21374        }
21375
21376        fn two() {}"#
21377            .unindent(),
21378    );
21379    set_up_lsp_handlers(false, &mut cx);
21380    let navigated = cx
21381        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21382        .await
21383        .expect("Failed to navigate to definition");
21384    assert_eq!(
21385        navigated,
21386        Navigated::Yes,
21387        "Should have navigated to definition from the GetDefinition response"
21388    );
21389    cx.assert_editor_state(
21390        &r#"fn one() {
21391            let mut a = two();
21392        }
21393
21394        fn «twoˇ»() {}"#
21395            .unindent(),
21396    );
21397
21398    let editors = cx.update_workspace(|workspace, _, cx| {
21399        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21400    });
21401    cx.update_editor(|_, _, test_editor_cx| {
21402        assert_eq!(
21403            editors.len(),
21404            1,
21405            "Initially, only one, test, editor should be open in the workspace"
21406        );
21407        assert_eq!(
21408            test_editor_cx.entity(),
21409            editors.last().expect("Asserted len is 1").clone()
21410        );
21411    });
21412
21413    set_up_lsp_handlers(true, &mut cx);
21414    let navigated = cx
21415        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21416        .await
21417        .expect("Failed to navigate to lookup references");
21418    assert_eq!(
21419        navigated,
21420        Navigated::Yes,
21421        "Should have navigated to references as a fallback after empty GoToDefinition response"
21422    );
21423    // We should not change the selections in the existing file,
21424    // if opening another milti buffer with the references
21425    cx.assert_editor_state(
21426        &r#"fn one() {
21427            let mut a = two();
21428        }
21429
21430        fn «twoˇ»() {}"#
21431            .unindent(),
21432    );
21433    let editors = cx.update_workspace(|workspace, _, cx| {
21434        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21435    });
21436    cx.update_editor(|_, _, test_editor_cx| {
21437        assert_eq!(
21438            editors.len(),
21439            2,
21440            "After falling back to references search, we open a new editor with the results"
21441        );
21442        let references_fallback_text = editors
21443            .into_iter()
21444            .find(|new_editor| *new_editor != test_editor_cx.entity())
21445            .expect("Should have one non-test editor now")
21446            .read(test_editor_cx)
21447            .text(test_editor_cx);
21448        assert_eq!(
21449            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
21450            "Should use the range from the references response and not the GoToDefinition one"
21451        );
21452    });
21453}
21454
21455#[gpui::test]
21456async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
21457    init_test(cx, |_| {});
21458    cx.update(|cx| {
21459        let mut editor_settings = EditorSettings::get_global(cx).clone();
21460        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
21461        EditorSettings::override_global(editor_settings, cx);
21462    });
21463    let mut cx = EditorLspTestContext::new_rust(
21464        lsp::ServerCapabilities {
21465            definition_provider: Some(lsp::OneOf::Left(true)),
21466            references_provider: Some(lsp::OneOf::Left(true)),
21467            ..lsp::ServerCapabilities::default()
21468        },
21469        cx,
21470    )
21471    .await;
21472    let original_state = r#"fn one() {
21473        let mut a = ˇtwo();
21474    }
21475
21476    fn two() {}"#
21477        .unindent();
21478    cx.set_state(&original_state);
21479
21480    let mut go_to_definition = cx
21481        .lsp
21482        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21483            move |_, _| async move { Ok(None) },
21484        );
21485    let _references = cx
21486        .lsp
21487        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
21488            panic!("Should not call for references with no go to definition fallback")
21489        });
21490
21491    let navigated = cx
21492        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21493        .await
21494        .expect("Failed to navigate to lookup references");
21495    go_to_definition
21496        .next()
21497        .await
21498        .expect("Should have called the go_to_definition handler");
21499
21500    assert_eq!(
21501        navigated,
21502        Navigated::No,
21503        "Should have navigated to references as a fallback after empty GoToDefinition response"
21504    );
21505    cx.assert_editor_state(&original_state);
21506    let editors = cx.update_workspace(|workspace, _, cx| {
21507        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21508    });
21509    cx.update_editor(|_, _, _| {
21510        assert_eq!(
21511            editors.len(),
21512            1,
21513            "After unsuccessful fallback, no other editor should have been opened"
21514        );
21515    });
21516}
21517
21518#[gpui::test]
21519async fn test_find_all_references_editor_reuse(cx: &mut TestAppContext) {
21520    init_test(cx, |_| {});
21521    let mut cx = EditorLspTestContext::new_rust(
21522        lsp::ServerCapabilities {
21523            references_provider: Some(lsp::OneOf::Left(true)),
21524            ..lsp::ServerCapabilities::default()
21525        },
21526        cx,
21527    )
21528    .await;
21529
21530    cx.set_state(
21531        &r#"
21532        fn one() {
21533            let mut a = two();
21534        }
21535
21536        fn ˇtwo() {}"#
21537            .unindent(),
21538    );
21539    cx.lsp
21540        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21541            Ok(Some(vec![
21542                lsp::Location {
21543                    uri: params.text_document_position.text_document.uri.clone(),
21544                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
21545                },
21546                lsp::Location {
21547                    uri: params.text_document_position.text_document.uri,
21548                    range: lsp::Range::new(lsp::Position::new(4, 4), lsp::Position::new(4, 7)),
21549                },
21550            ]))
21551        });
21552    let navigated = cx
21553        .update_editor(|editor, window, cx| {
21554            editor.find_all_references(&FindAllReferences, window, cx)
21555        })
21556        .unwrap()
21557        .await
21558        .expect("Failed to navigate to references");
21559    assert_eq!(
21560        navigated,
21561        Navigated::Yes,
21562        "Should have navigated to references from the FindAllReferences response"
21563    );
21564    cx.assert_editor_state(
21565        &r#"fn one() {
21566            let mut a = two();
21567        }
21568
21569        fn ˇtwo() {}"#
21570            .unindent(),
21571    );
21572
21573    let editors = cx.update_workspace(|workspace, _, cx| {
21574        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21575    });
21576    cx.update_editor(|_, _, _| {
21577        assert_eq!(editors.len(), 2, "We should have opened a new multibuffer");
21578    });
21579
21580    cx.set_state(
21581        &r#"fn one() {
21582            let mut a = ˇtwo();
21583        }
21584
21585        fn two() {}"#
21586            .unindent(),
21587    );
21588    let navigated = cx
21589        .update_editor(|editor, window, cx| {
21590            editor.find_all_references(&FindAllReferences, window, cx)
21591        })
21592        .unwrap()
21593        .await
21594        .expect("Failed to navigate to references");
21595    assert_eq!(
21596        navigated,
21597        Navigated::Yes,
21598        "Should have navigated to references from the FindAllReferences response"
21599    );
21600    cx.assert_editor_state(
21601        &r#"fn one() {
21602            let mut a = ˇtwo();
21603        }
21604
21605        fn two() {}"#
21606            .unindent(),
21607    );
21608    let editors = cx.update_workspace(|workspace, _, cx| {
21609        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21610    });
21611    cx.update_editor(|_, _, _| {
21612        assert_eq!(
21613            editors.len(),
21614            2,
21615            "should have re-used the previous multibuffer"
21616        );
21617    });
21618
21619    cx.set_state(
21620        &r#"fn one() {
21621            let mut a = ˇtwo();
21622        }
21623        fn three() {}
21624        fn two() {}"#
21625            .unindent(),
21626    );
21627    cx.lsp
21628        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21629            Ok(Some(vec![
21630                lsp::Location {
21631                    uri: params.text_document_position.text_document.uri.clone(),
21632                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
21633                },
21634                lsp::Location {
21635                    uri: params.text_document_position.text_document.uri,
21636                    range: lsp::Range::new(lsp::Position::new(5, 4), lsp::Position::new(5, 7)),
21637                },
21638            ]))
21639        });
21640    let navigated = cx
21641        .update_editor(|editor, window, cx| {
21642            editor.find_all_references(&FindAllReferences, window, cx)
21643        })
21644        .unwrap()
21645        .await
21646        .expect("Failed to navigate to references");
21647    assert_eq!(
21648        navigated,
21649        Navigated::Yes,
21650        "Should have navigated to references from the FindAllReferences response"
21651    );
21652    cx.assert_editor_state(
21653        &r#"fn one() {
21654                let mut a = ˇtwo();
21655            }
21656            fn three() {}
21657            fn two() {}"#
21658            .unindent(),
21659    );
21660    let editors = cx.update_workspace(|workspace, _, cx| {
21661        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21662    });
21663    cx.update_editor(|_, _, _| {
21664        assert_eq!(
21665            editors.len(),
21666            3,
21667            "should have used a new multibuffer as offsets changed"
21668        );
21669    });
21670}
21671#[gpui::test]
21672async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
21673    init_test(cx, |_| {});
21674
21675    let language = Arc::new(Language::new(
21676        LanguageConfig::default(),
21677        Some(tree_sitter_rust::LANGUAGE.into()),
21678    ));
21679
21680    let text = r#"
21681        #[cfg(test)]
21682        mod tests() {
21683            #[test]
21684            fn runnable_1() {
21685                let a = 1;
21686            }
21687
21688            #[test]
21689            fn runnable_2() {
21690                let a = 1;
21691                let b = 2;
21692            }
21693        }
21694    "#
21695    .unindent();
21696
21697    let fs = FakeFs::new(cx.executor());
21698    fs.insert_file("/file.rs", Default::default()).await;
21699
21700    let project = Project::test(fs, ["/a".as_ref()], cx).await;
21701    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21702    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21703    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
21704    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
21705
21706    let editor = cx.new_window_entity(|window, cx| {
21707        Editor::new(
21708            EditorMode::full(),
21709            multi_buffer,
21710            Some(project.clone()),
21711            window,
21712            cx,
21713        )
21714    });
21715
21716    editor.update_in(cx, |editor, window, cx| {
21717        let snapshot = editor.buffer().read(cx).snapshot(cx);
21718        editor.tasks.insert(
21719            (buffer.read(cx).remote_id(), 3),
21720            RunnableTasks {
21721                templates: vec![],
21722                offset: snapshot.anchor_before(43),
21723                column: 0,
21724                extra_variables: HashMap::default(),
21725                context_range: BufferOffset(43)..BufferOffset(85),
21726            },
21727        );
21728        editor.tasks.insert(
21729            (buffer.read(cx).remote_id(), 8),
21730            RunnableTasks {
21731                templates: vec![],
21732                offset: snapshot.anchor_before(86),
21733                column: 0,
21734                extra_variables: HashMap::default(),
21735                context_range: BufferOffset(86)..BufferOffset(191),
21736            },
21737        );
21738
21739        // Test finding task when cursor is inside function body
21740        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21741            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
21742        });
21743        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
21744        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
21745
21746        // Test finding task when cursor is on function name
21747        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21748            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
21749        });
21750        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
21751        assert_eq!(row, 8, "Should find task when cursor is on function name");
21752    });
21753}
21754
21755#[gpui::test]
21756async fn test_folding_buffers(cx: &mut TestAppContext) {
21757    init_test(cx, |_| {});
21758
21759    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
21760    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
21761    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
21762
21763    let fs = FakeFs::new(cx.executor());
21764    fs.insert_tree(
21765        path!("/a"),
21766        json!({
21767            "first.rs": sample_text_1,
21768            "second.rs": sample_text_2,
21769            "third.rs": sample_text_3,
21770        }),
21771    )
21772    .await;
21773    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21774    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21775    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21776    let worktree = project.update(cx, |project, cx| {
21777        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
21778        assert_eq!(worktrees.len(), 1);
21779        worktrees.pop().unwrap()
21780    });
21781    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
21782
21783    let buffer_1 = project
21784        .update(cx, |project, cx| {
21785            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
21786        })
21787        .await
21788        .unwrap();
21789    let buffer_2 = project
21790        .update(cx, |project, cx| {
21791            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
21792        })
21793        .await
21794        .unwrap();
21795    let buffer_3 = project
21796        .update(cx, |project, cx| {
21797            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
21798        })
21799        .await
21800        .unwrap();
21801
21802    let multi_buffer = cx.new(|cx| {
21803        let mut multi_buffer = MultiBuffer::new(ReadWrite);
21804        multi_buffer.push_excerpts(
21805            buffer_1.clone(),
21806            [
21807                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21808                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21809                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21810            ],
21811            cx,
21812        );
21813        multi_buffer.push_excerpts(
21814            buffer_2.clone(),
21815            [
21816                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21817                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21818                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21819            ],
21820            cx,
21821        );
21822        multi_buffer.push_excerpts(
21823            buffer_3.clone(),
21824            [
21825                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21826                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21827                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21828            ],
21829            cx,
21830        );
21831        multi_buffer
21832    });
21833    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
21834        Editor::new(
21835            EditorMode::full(),
21836            multi_buffer.clone(),
21837            Some(project.clone()),
21838            window,
21839            cx,
21840        )
21841    });
21842
21843    assert_eq!(
21844        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21845        "\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",
21846    );
21847
21848    multi_buffer_editor.update(cx, |editor, cx| {
21849        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
21850    });
21851    assert_eq!(
21852        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21853        "\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",
21854        "After folding the first buffer, its text should not be displayed"
21855    );
21856
21857    multi_buffer_editor.update(cx, |editor, cx| {
21858        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
21859    });
21860    assert_eq!(
21861        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21862        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
21863        "After folding the second buffer, its text should not be displayed"
21864    );
21865
21866    multi_buffer_editor.update(cx, |editor, cx| {
21867        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
21868    });
21869    assert_eq!(
21870        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21871        "\n\n\n\n\n",
21872        "After folding the third buffer, its text should not be displayed"
21873    );
21874
21875    // Emulate selection inside the fold logic, that should work
21876    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21877        editor
21878            .snapshot(window, cx)
21879            .next_line_boundary(Point::new(0, 4));
21880    });
21881
21882    multi_buffer_editor.update(cx, |editor, cx| {
21883        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
21884    });
21885    assert_eq!(
21886        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21887        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
21888        "After unfolding the second buffer, its text should be displayed"
21889    );
21890
21891    // Typing inside of buffer 1 causes that buffer to be unfolded.
21892    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21893        assert_eq!(
21894            multi_buffer
21895                .read(cx)
21896                .snapshot(cx)
21897                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
21898                .collect::<String>(),
21899            "bbbb"
21900        );
21901        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21902            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
21903        });
21904        editor.handle_input("B", window, cx);
21905    });
21906
21907    assert_eq!(
21908        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21909        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
21910        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
21911    );
21912
21913    multi_buffer_editor.update(cx, |editor, cx| {
21914        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
21915    });
21916    assert_eq!(
21917        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21918        "\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",
21919        "After unfolding the all buffers, all original text should be displayed"
21920    );
21921}
21922
21923#[gpui::test]
21924async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
21925    init_test(cx, |_| {});
21926
21927    let sample_text_1 = "1111\n2222\n3333".to_string();
21928    let sample_text_2 = "4444\n5555\n6666".to_string();
21929    let sample_text_3 = "7777\n8888\n9999".to_string();
21930
21931    let fs = FakeFs::new(cx.executor());
21932    fs.insert_tree(
21933        path!("/a"),
21934        json!({
21935            "first.rs": sample_text_1,
21936            "second.rs": sample_text_2,
21937            "third.rs": sample_text_3,
21938        }),
21939    )
21940    .await;
21941    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21942    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21943    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21944    let worktree = project.update(cx, |project, cx| {
21945        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
21946        assert_eq!(worktrees.len(), 1);
21947        worktrees.pop().unwrap()
21948    });
21949    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
21950
21951    let buffer_1 = project
21952        .update(cx, |project, cx| {
21953            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
21954        })
21955        .await
21956        .unwrap();
21957    let buffer_2 = project
21958        .update(cx, |project, cx| {
21959            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
21960        })
21961        .await
21962        .unwrap();
21963    let buffer_3 = project
21964        .update(cx, |project, cx| {
21965            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
21966        })
21967        .await
21968        .unwrap();
21969
21970    let multi_buffer = cx.new(|cx| {
21971        let mut multi_buffer = MultiBuffer::new(ReadWrite);
21972        multi_buffer.push_excerpts(
21973            buffer_1.clone(),
21974            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21975            cx,
21976        );
21977        multi_buffer.push_excerpts(
21978            buffer_2.clone(),
21979            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21980            cx,
21981        );
21982        multi_buffer.push_excerpts(
21983            buffer_3.clone(),
21984            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21985            cx,
21986        );
21987        multi_buffer
21988    });
21989
21990    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
21991        Editor::new(
21992            EditorMode::full(),
21993            multi_buffer,
21994            Some(project.clone()),
21995            window,
21996            cx,
21997        )
21998    });
21999
22000    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
22001    assert_eq!(
22002        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22003        full_text,
22004    );
22005
22006    multi_buffer_editor.update(cx, |editor, cx| {
22007        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
22008    });
22009    assert_eq!(
22010        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22011        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
22012        "After folding the first buffer, its text should not be displayed"
22013    );
22014
22015    multi_buffer_editor.update(cx, |editor, cx| {
22016        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
22017    });
22018
22019    assert_eq!(
22020        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22021        "\n\n\n\n\n\n7777\n8888\n9999",
22022        "After folding the second buffer, its text should not be displayed"
22023    );
22024
22025    multi_buffer_editor.update(cx, |editor, cx| {
22026        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
22027    });
22028    assert_eq!(
22029        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22030        "\n\n\n\n\n",
22031        "After folding the third buffer, its text should not be displayed"
22032    );
22033
22034    multi_buffer_editor.update(cx, |editor, cx| {
22035        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
22036    });
22037    assert_eq!(
22038        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22039        "\n\n\n\n4444\n5555\n6666\n\n",
22040        "After unfolding the second buffer, its text should be displayed"
22041    );
22042
22043    multi_buffer_editor.update(cx, |editor, cx| {
22044        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
22045    });
22046    assert_eq!(
22047        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22048        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
22049        "After unfolding the first buffer, its text should be displayed"
22050    );
22051
22052    multi_buffer_editor.update(cx, |editor, cx| {
22053        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
22054    });
22055    assert_eq!(
22056        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22057        full_text,
22058        "After unfolding all buffers, all original text should be displayed"
22059    );
22060}
22061
22062#[gpui::test]
22063async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
22064    init_test(cx, |_| {});
22065
22066    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
22067
22068    let fs = FakeFs::new(cx.executor());
22069    fs.insert_tree(
22070        path!("/a"),
22071        json!({
22072            "main.rs": sample_text,
22073        }),
22074    )
22075    .await;
22076    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22077    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22078    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22079    let worktree = project.update(cx, |project, cx| {
22080        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22081        assert_eq!(worktrees.len(), 1);
22082        worktrees.pop().unwrap()
22083    });
22084    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22085
22086    let buffer_1 = project
22087        .update(cx, |project, cx| {
22088            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22089        })
22090        .await
22091        .unwrap();
22092
22093    let multi_buffer = cx.new(|cx| {
22094        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22095        multi_buffer.push_excerpts(
22096            buffer_1.clone(),
22097            [ExcerptRange::new(
22098                Point::new(0, 0)
22099                    ..Point::new(
22100                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
22101                        0,
22102                    ),
22103            )],
22104            cx,
22105        );
22106        multi_buffer
22107    });
22108    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22109        Editor::new(
22110            EditorMode::full(),
22111            multi_buffer,
22112            Some(project.clone()),
22113            window,
22114            cx,
22115        )
22116    });
22117
22118    let selection_range = Point::new(1, 0)..Point::new(2, 0);
22119    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22120        enum TestHighlight {}
22121        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
22122        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
22123        editor.highlight_text::<TestHighlight>(
22124            vec![highlight_range.clone()],
22125            HighlightStyle::color(Hsla::green()),
22126            cx,
22127        );
22128        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22129            s.select_ranges(Some(highlight_range))
22130        });
22131    });
22132
22133    let full_text = format!("\n\n{sample_text}");
22134    assert_eq!(
22135        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22136        full_text,
22137    );
22138}
22139
22140#[gpui::test]
22141async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
22142    init_test(cx, |_| {});
22143    cx.update(|cx| {
22144        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
22145            "keymaps/default-linux.json",
22146            cx,
22147        )
22148        .unwrap();
22149        cx.bind_keys(default_key_bindings);
22150    });
22151
22152    let (editor, cx) = cx.add_window_view(|window, cx| {
22153        let multi_buffer = MultiBuffer::build_multi(
22154            [
22155                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
22156                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
22157                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
22158                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
22159            ],
22160            cx,
22161        );
22162        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
22163
22164        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
22165        // fold all but the second buffer, so that we test navigating between two
22166        // adjacent folded buffers, as well as folded buffers at the start and
22167        // end the multibuffer
22168        editor.fold_buffer(buffer_ids[0], cx);
22169        editor.fold_buffer(buffer_ids[2], cx);
22170        editor.fold_buffer(buffer_ids[3], cx);
22171
22172        editor
22173    });
22174    cx.simulate_resize(size(px(1000.), px(1000.)));
22175
22176    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
22177    cx.assert_excerpts_with_selections(indoc! {"
22178        [EXCERPT]
22179        ˇ[FOLDED]
22180        [EXCERPT]
22181        a1
22182        b1
22183        [EXCERPT]
22184        [FOLDED]
22185        [EXCERPT]
22186        [FOLDED]
22187        "
22188    });
22189    cx.simulate_keystroke("down");
22190    cx.assert_excerpts_with_selections(indoc! {"
22191        [EXCERPT]
22192        [FOLDED]
22193        [EXCERPT]
22194        ˇa1
22195        b1
22196        [EXCERPT]
22197        [FOLDED]
22198        [EXCERPT]
22199        [FOLDED]
22200        "
22201    });
22202    cx.simulate_keystroke("down");
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("down");
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("down");
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    for _ in 0..5 {
22242        cx.simulate_keystroke("down");
22243        cx.assert_excerpts_with_selections(indoc! {"
22244            [EXCERPT]
22245            [FOLDED]
22246            [EXCERPT]
22247            a1
22248            b1
22249            [EXCERPT]
22250            [FOLDED]
22251            [EXCERPT]
22252            ˇ[FOLDED]
22253            "
22254        });
22255    }
22256
22257    cx.simulate_keystroke("up");
22258    cx.assert_excerpts_with_selections(indoc! {"
22259        [EXCERPT]
22260        [FOLDED]
22261        [EXCERPT]
22262        a1
22263        b1
22264        [EXCERPT]
22265        ˇ[FOLDED]
22266        [EXCERPT]
22267        [FOLDED]
22268        "
22269    });
22270    cx.simulate_keystroke("up");
22271    cx.assert_excerpts_with_selections(indoc! {"
22272        [EXCERPT]
22273        [FOLDED]
22274        [EXCERPT]
22275        a1
22276        b1
22277        ˇ[EXCERPT]
22278        [FOLDED]
22279        [EXCERPT]
22280        [FOLDED]
22281        "
22282    });
22283    cx.simulate_keystroke("up");
22284    cx.assert_excerpts_with_selections(indoc! {"
22285        [EXCERPT]
22286        [FOLDED]
22287        [EXCERPT]
22288        a1
22289        ˇb1
22290        [EXCERPT]
22291        [FOLDED]
22292        [EXCERPT]
22293        [FOLDED]
22294        "
22295    });
22296    cx.simulate_keystroke("up");
22297    cx.assert_excerpts_with_selections(indoc! {"
22298        [EXCERPT]
22299        [FOLDED]
22300        [EXCERPT]
22301        ˇa1
22302        b1
22303        [EXCERPT]
22304        [FOLDED]
22305        [EXCERPT]
22306        [FOLDED]
22307        "
22308    });
22309    for _ in 0..5 {
22310        cx.simulate_keystroke("up");
22311        cx.assert_excerpts_with_selections(indoc! {"
22312            [EXCERPT]
22313            ˇ[FOLDED]
22314            [EXCERPT]
22315            a1
22316            b1
22317            [EXCERPT]
22318            [FOLDED]
22319            [EXCERPT]
22320            [FOLDED]
22321            "
22322        });
22323    }
22324}
22325
22326#[gpui::test]
22327async fn test_edit_prediction_text(cx: &mut TestAppContext) {
22328    init_test(cx, |_| {});
22329
22330    // Simple insertion
22331    assert_highlighted_edits(
22332        "Hello, world!",
22333        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
22334        true,
22335        cx,
22336        |highlighted_edits, cx| {
22337            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
22338            assert_eq!(highlighted_edits.highlights.len(), 1);
22339            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
22340            assert_eq!(
22341                highlighted_edits.highlights[0].1.background_color,
22342                Some(cx.theme().status().created_background)
22343            );
22344        },
22345    )
22346    .await;
22347
22348    // Replacement
22349    assert_highlighted_edits(
22350        "This is a test.",
22351        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
22352        false,
22353        cx,
22354        |highlighted_edits, cx| {
22355            assert_eq!(highlighted_edits.text, "That is a test.");
22356            assert_eq!(highlighted_edits.highlights.len(), 1);
22357            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
22358            assert_eq!(
22359                highlighted_edits.highlights[0].1.background_color,
22360                Some(cx.theme().status().created_background)
22361            );
22362        },
22363    )
22364    .await;
22365
22366    // Multiple edits
22367    assert_highlighted_edits(
22368        "Hello, world!",
22369        vec![
22370            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
22371            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
22372        ],
22373        false,
22374        cx,
22375        |highlighted_edits, cx| {
22376            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
22377            assert_eq!(highlighted_edits.highlights.len(), 2);
22378            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
22379            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
22380            assert_eq!(
22381                highlighted_edits.highlights[0].1.background_color,
22382                Some(cx.theme().status().created_background)
22383            );
22384            assert_eq!(
22385                highlighted_edits.highlights[1].1.background_color,
22386                Some(cx.theme().status().created_background)
22387            );
22388        },
22389    )
22390    .await;
22391
22392    // Multiple lines with edits
22393    assert_highlighted_edits(
22394        "First line\nSecond line\nThird line\nFourth line",
22395        vec![
22396            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
22397            (
22398                Point::new(2, 0)..Point::new(2, 10),
22399                "New third line".to_string(),
22400            ),
22401            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
22402        ],
22403        false,
22404        cx,
22405        |highlighted_edits, cx| {
22406            assert_eq!(
22407                highlighted_edits.text,
22408                "Second modified\nNew third line\nFourth updated line"
22409            );
22410            assert_eq!(highlighted_edits.highlights.len(), 3);
22411            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
22412            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
22413            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
22414            for highlight in &highlighted_edits.highlights {
22415                assert_eq!(
22416                    highlight.1.background_color,
22417                    Some(cx.theme().status().created_background)
22418                );
22419            }
22420        },
22421    )
22422    .await;
22423}
22424
22425#[gpui::test]
22426async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
22427    init_test(cx, |_| {});
22428
22429    // Deletion
22430    assert_highlighted_edits(
22431        "Hello, world!",
22432        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
22433        true,
22434        cx,
22435        |highlighted_edits, cx| {
22436            assert_eq!(highlighted_edits.text, "Hello, world!");
22437            assert_eq!(highlighted_edits.highlights.len(), 1);
22438            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
22439            assert_eq!(
22440                highlighted_edits.highlights[0].1.background_color,
22441                Some(cx.theme().status().deleted_background)
22442            );
22443        },
22444    )
22445    .await;
22446
22447    // Insertion
22448    assert_highlighted_edits(
22449        "Hello, world!",
22450        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
22451        true,
22452        cx,
22453        |highlighted_edits, cx| {
22454            assert_eq!(highlighted_edits.highlights.len(), 1);
22455            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
22456            assert_eq!(
22457                highlighted_edits.highlights[0].1.background_color,
22458                Some(cx.theme().status().created_background)
22459            );
22460        },
22461    )
22462    .await;
22463}
22464
22465async fn assert_highlighted_edits(
22466    text: &str,
22467    edits: Vec<(Range<Point>, String)>,
22468    include_deletions: bool,
22469    cx: &mut TestAppContext,
22470    assertion_fn: impl Fn(HighlightedText, &App),
22471) {
22472    let window = cx.add_window(|window, cx| {
22473        let buffer = MultiBuffer::build_simple(text, cx);
22474        Editor::new(EditorMode::full(), buffer, None, window, cx)
22475    });
22476    let cx = &mut VisualTestContext::from_window(*window, cx);
22477
22478    let (buffer, snapshot) = window
22479        .update(cx, |editor, _window, cx| {
22480            (
22481                editor.buffer().clone(),
22482                editor.buffer().read(cx).snapshot(cx),
22483            )
22484        })
22485        .unwrap();
22486
22487    let edits = edits
22488        .into_iter()
22489        .map(|(range, edit)| {
22490            (
22491                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
22492                edit,
22493            )
22494        })
22495        .collect::<Vec<_>>();
22496
22497    let text_anchor_edits = edits
22498        .clone()
22499        .into_iter()
22500        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
22501        .collect::<Vec<_>>();
22502
22503    let edit_preview = window
22504        .update(cx, |_, _window, cx| {
22505            buffer
22506                .read(cx)
22507                .as_singleton()
22508                .unwrap()
22509                .read(cx)
22510                .preview_edits(text_anchor_edits.into(), cx)
22511        })
22512        .unwrap()
22513        .await;
22514
22515    cx.update(|_window, cx| {
22516        let highlighted_edits = edit_prediction_edit_text(
22517            snapshot.as_singleton().unwrap().2,
22518            &edits,
22519            &edit_preview,
22520            include_deletions,
22521            cx,
22522        );
22523        assertion_fn(highlighted_edits, cx)
22524    });
22525}
22526
22527#[track_caller]
22528fn assert_breakpoint(
22529    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
22530    path: &Arc<Path>,
22531    expected: Vec<(u32, Breakpoint)>,
22532) {
22533    if expected.is_empty() {
22534        assert!(!breakpoints.contains_key(path), "{}", path.display());
22535    } else {
22536        let mut breakpoint = breakpoints
22537            .get(path)
22538            .unwrap()
22539            .iter()
22540            .map(|breakpoint| {
22541                (
22542                    breakpoint.row,
22543                    Breakpoint {
22544                        message: breakpoint.message.clone(),
22545                        state: breakpoint.state,
22546                        condition: breakpoint.condition.clone(),
22547                        hit_condition: breakpoint.hit_condition.clone(),
22548                    },
22549                )
22550            })
22551            .collect::<Vec<_>>();
22552
22553        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
22554
22555        assert_eq!(expected, breakpoint);
22556    }
22557}
22558
22559fn add_log_breakpoint_at_cursor(
22560    editor: &mut Editor,
22561    log_message: &str,
22562    window: &mut Window,
22563    cx: &mut Context<Editor>,
22564) {
22565    let (anchor, bp) = editor
22566        .breakpoints_at_cursors(window, cx)
22567        .first()
22568        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
22569        .unwrap_or_else(|| {
22570            let cursor_position: Point = editor.selections.newest(cx).head();
22571
22572            let breakpoint_position = editor
22573                .snapshot(window, cx)
22574                .display_snapshot
22575                .buffer_snapshot()
22576                .anchor_before(Point::new(cursor_position.row, 0));
22577
22578            (breakpoint_position, Breakpoint::new_log(log_message))
22579        });
22580
22581    editor.edit_breakpoint_at_anchor(
22582        anchor,
22583        bp,
22584        BreakpointEditAction::EditLogMessage(log_message.into()),
22585        cx,
22586    );
22587}
22588
22589#[gpui::test]
22590async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
22591    init_test(cx, |_| {});
22592
22593    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22594    let fs = FakeFs::new(cx.executor());
22595    fs.insert_tree(
22596        path!("/a"),
22597        json!({
22598            "main.rs": sample_text,
22599        }),
22600    )
22601    .await;
22602    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22603    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22604    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22605
22606    let fs = FakeFs::new(cx.executor());
22607    fs.insert_tree(
22608        path!("/a"),
22609        json!({
22610            "main.rs": sample_text,
22611        }),
22612    )
22613    .await;
22614    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22615    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22616    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22617    let worktree_id = workspace
22618        .update(cx, |workspace, _window, cx| {
22619            workspace.project().update(cx, |project, cx| {
22620                project.worktrees(cx).next().unwrap().read(cx).id()
22621            })
22622        })
22623        .unwrap();
22624
22625    let buffer = project
22626        .update(cx, |project, cx| {
22627            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22628        })
22629        .await
22630        .unwrap();
22631
22632    let (editor, cx) = cx.add_window_view(|window, cx| {
22633        Editor::new(
22634            EditorMode::full(),
22635            MultiBuffer::build_from_buffer(buffer, cx),
22636            Some(project.clone()),
22637            window,
22638            cx,
22639        )
22640    });
22641
22642    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22643    let abs_path = project.read_with(cx, |project, cx| {
22644        project
22645            .absolute_path(&project_path, cx)
22646            .map(Arc::from)
22647            .unwrap()
22648    });
22649
22650    // assert we can add breakpoint on the first line
22651    editor.update_in(cx, |editor, window, cx| {
22652        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22653        editor.move_to_end(&MoveToEnd, window, cx);
22654        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22655    });
22656
22657    let breakpoints = editor.update(cx, |editor, cx| {
22658        editor
22659            .breakpoint_store()
22660            .as_ref()
22661            .unwrap()
22662            .read(cx)
22663            .all_source_breakpoints(cx)
22664    });
22665
22666    assert_eq!(1, breakpoints.len());
22667    assert_breakpoint(
22668        &breakpoints,
22669        &abs_path,
22670        vec![
22671            (0, Breakpoint::new_standard()),
22672            (3, Breakpoint::new_standard()),
22673        ],
22674    );
22675
22676    editor.update_in(cx, |editor, window, cx| {
22677        editor.move_to_beginning(&MoveToBeginning, window, cx);
22678        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22679    });
22680
22681    let breakpoints = editor.update(cx, |editor, cx| {
22682        editor
22683            .breakpoint_store()
22684            .as_ref()
22685            .unwrap()
22686            .read(cx)
22687            .all_source_breakpoints(cx)
22688    });
22689
22690    assert_eq!(1, breakpoints.len());
22691    assert_breakpoint(
22692        &breakpoints,
22693        &abs_path,
22694        vec![(3, Breakpoint::new_standard())],
22695    );
22696
22697    editor.update_in(cx, |editor, window, cx| {
22698        editor.move_to_end(&MoveToEnd, window, cx);
22699        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22700    });
22701
22702    let breakpoints = editor.update(cx, |editor, cx| {
22703        editor
22704            .breakpoint_store()
22705            .as_ref()
22706            .unwrap()
22707            .read(cx)
22708            .all_source_breakpoints(cx)
22709    });
22710
22711    assert_eq!(0, breakpoints.len());
22712    assert_breakpoint(&breakpoints, &abs_path, vec![]);
22713}
22714
22715#[gpui::test]
22716async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
22717    init_test(cx, |_| {});
22718
22719    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22720
22721    let fs = FakeFs::new(cx.executor());
22722    fs.insert_tree(
22723        path!("/a"),
22724        json!({
22725            "main.rs": sample_text,
22726        }),
22727    )
22728    .await;
22729    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22730    let (workspace, cx) =
22731        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22732
22733    let worktree_id = workspace.update(cx, |workspace, cx| {
22734        workspace.project().update(cx, |project, cx| {
22735            project.worktrees(cx).next().unwrap().read(cx).id()
22736        })
22737    });
22738
22739    let buffer = project
22740        .update(cx, |project, cx| {
22741            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22742        })
22743        .await
22744        .unwrap();
22745
22746    let (editor, cx) = cx.add_window_view(|window, cx| {
22747        Editor::new(
22748            EditorMode::full(),
22749            MultiBuffer::build_from_buffer(buffer, cx),
22750            Some(project.clone()),
22751            window,
22752            cx,
22753        )
22754    });
22755
22756    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22757    let abs_path = project.read_with(cx, |project, cx| {
22758        project
22759            .absolute_path(&project_path, cx)
22760            .map(Arc::from)
22761            .unwrap()
22762    });
22763
22764    editor.update_in(cx, |editor, window, cx| {
22765        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
22766    });
22767
22768    let breakpoints = editor.update(cx, |editor, cx| {
22769        editor
22770            .breakpoint_store()
22771            .as_ref()
22772            .unwrap()
22773            .read(cx)
22774            .all_source_breakpoints(cx)
22775    });
22776
22777    assert_breakpoint(
22778        &breakpoints,
22779        &abs_path,
22780        vec![(0, Breakpoint::new_log("hello world"))],
22781    );
22782
22783    // Removing a log message from a log breakpoint should remove it
22784    editor.update_in(cx, |editor, window, cx| {
22785        add_log_breakpoint_at_cursor(editor, "", window, cx);
22786    });
22787
22788    let breakpoints = editor.update(cx, |editor, cx| {
22789        editor
22790            .breakpoint_store()
22791            .as_ref()
22792            .unwrap()
22793            .read(cx)
22794            .all_source_breakpoints(cx)
22795    });
22796
22797    assert_breakpoint(&breakpoints, &abs_path, vec![]);
22798
22799    editor.update_in(cx, |editor, window, cx| {
22800        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22801        editor.move_to_end(&MoveToEnd, window, cx);
22802        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22803        // Not adding a log message to a standard breakpoint shouldn't remove it
22804        add_log_breakpoint_at_cursor(editor, "", window, cx);
22805    });
22806
22807    let breakpoints = editor.update(cx, |editor, cx| {
22808        editor
22809            .breakpoint_store()
22810            .as_ref()
22811            .unwrap()
22812            .read(cx)
22813            .all_source_breakpoints(cx)
22814    });
22815
22816    assert_breakpoint(
22817        &breakpoints,
22818        &abs_path,
22819        vec![
22820            (0, Breakpoint::new_standard()),
22821            (3, Breakpoint::new_standard()),
22822        ],
22823    );
22824
22825    editor.update_in(cx, |editor, window, cx| {
22826        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
22827    });
22828
22829    let breakpoints = editor.update(cx, |editor, cx| {
22830        editor
22831            .breakpoint_store()
22832            .as_ref()
22833            .unwrap()
22834            .read(cx)
22835            .all_source_breakpoints(cx)
22836    });
22837
22838    assert_breakpoint(
22839        &breakpoints,
22840        &abs_path,
22841        vec![
22842            (0, Breakpoint::new_standard()),
22843            (3, Breakpoint::new_log("hello world")),
22844        ],
22845    );
22846
22847    editor.update_in(cx, |editor, window, cx| {
22848        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
22849    });
22850
22851    let breakpoints = editor.update(cx, |editor, cx| {
22852        editor
22853            .breakpoint_store()
22854            .as_ref()
22855            .unwrap()
22856            .read(cx)
22857            .all_source_breakpoints(cx)
22858    });
22859
22860    assert_breakpoint(
22861        &breakpoints,
22862        &abs_path,
22863        vec![
22864            (0, Breakpoint::new_standard()),
22865            (3, Breakpoint::new_log("hello Earth!!")),
22866        ],
22867    );
22868}
22869
22870/// This also tests that Editor::breakpoint_at_cursor_head is working properly
22871/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
22872/// or when breakpoints were placed out of order. This tests for a regression too
22873#[gpui::test]
22874async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
22875    init_test(cx, |_| {});
22876
22877    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22878    let fs = FakeFs::new(cx.executor());
22879    fs.insert_tree(
22880        path!("/a"),
22881        json!({
22882            "main.rs": sample_text,
22883        }),
22884    )
22885    .await;
22886    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22887    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22888    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22889
22890    let fs = FakeFs::new(cx.executor());
22891    fs.insert_tree(
22892        path!("/a"),
22893        json!({
22894            "main.rs": sample_text,
22895        }),
22896    )
22897    .await;
22898    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22899    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22900    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22901    let worktree_id = workspace
22902        .update(cx, |workspace, _window, cx| {
22903            workspace.project().update(cx, |project, cx| {
22904                project.worktrees(cx).next().unwrap().read(cx).id()
22905            })
22906        })
22907        .unwrap();
22908
22909    let buffer = project
22910        .update(cx, |project, cx| {
22911            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22912        })
22913        .await
22914        .unwrap();
22915
22916    let (editor, cx) = cx.add_window_view(|window, cx| {
22917        Editor::new(
22918            EditorMode::full(),
22919            MultiBuffer::build_from_buffer(buffer, cx),
22920            Some(project.clone()),
22921            window,
22922            cx,
22923        )
22924    });
22925
22926    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22927    let abs_path = project.read_with(cx, |project, cx| {
22928        project
22929            .absolute_path(&project_path, cx)
22930            .map(Arc::from)
22931            .unwrap()
22932    });
22933
22934    // assert we can add breakpoint on the first line
22935    editor.update_in(cx, |editor, window, cx| {
22936        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22937        editor.move_to_end(&MoveToEnd, window, cx);
22938        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22939        editor.move_up(&MoveUp, window, cx);
22940        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22941    });
22942
22943    let breakpoints = editor.update(cx, |editor, cx| {
22944        editor
22945            .breakpoint_store()
22946            .as_ref()
22947            .unwrap()
22948            .read(cx)
22949            .all_source_breakpoints(cx)
22950    });
22951
22952    assert_eq!(1, breakpoints.len());
22953    assert_breakpoint(
22954        &breakpoints,
22955        &abs_path,
22956        vec![
22957            (0, Breakpoint::new_standard()),
22958            (2, Breakpoint::new_standard()),
22959            (3, Breakpoint::new_standard()),
22960        ],
22961    );
22962
22963    editor.update_in(cx, |editor, window, cx| {
22964        editor.move_to_beginning(&MoveToBeginning, window, cx);
22965        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22966        editor.move_to_end(&MoveToEnd, window, cx);
22967        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22968        // Disabling a breakpoint that doesn't exist should do nothing
22969        editor.move_up(&MoveUp, window, cx);
22970        editor.move_up(&MoveUp, window, cx);
22971        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22972    });
22973
22974    let breakpoints = editor.update(cx, |editor, cx| {
22975        editor
22976            .breakpoint_store()
22977            .as_ref()
22978            .unwrap()
22979            .read(cx)
22980            .all_source_breakpoints(cx)
22981    });
22982
22983    let disable_breakpoint = {
22984        let mut bp = Breakpoint::new_standard();
22985        bp.state = BreakpointState::Disabled;
22986        bp
22987    };
22988
22989    assert_eq!(1, breakpoints.len());
22990    assert_breakpoint(
22991        &breakpoints,
22992        &abs_path,
22993        vec![
22994            (0, disable_breakpoint.clone()),
22995            (2, Breakpoint::new_standard()),
22996            (3, disable_breakpoint.clone()),
22997        ],
22998    );
22999
23000    editor.update_in(cx, |editor, window, cx| {
23001        editor.move_to_beginning(&MoveToBeginning, window, cx);
23002        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
23003        editor.move_to_end(&MoveToEnd, window, cx);
23004        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
23005        editor.move_up(&MoveUp, window, cx);
23006        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23007    });
23008
23009    let breakpoints = editor.update(cx, |editor, cx| {
23010        editor
23011            .breakpoint_store()
23012            .as_ref()
23013            .unwrap()
23014            .read(cx)
23015            .all_source_breakpoints(cx)
23016    });
23017
23018    assert_eq!(1, breakpoints.len());
23019    assert_breakpoint(
23020        &breakpoints,
23021        &abs_path,
23022        vec![
23023            (0, Breakpoint::new_standard()),
23024            (2, disable_breakpoint),
23025            (3, Breakpoint::new_standard()),
23026        ],
23027    );
23028}
23029
23030#[gpui::test]
23031async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
23032    init_test(cx, |_| {});
23033    let capabilities = lsp::ServerCapabilities {
23034        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
23035            prepare_provider: Some(true),
23036            work_done_progress_options: Default::default(),
23037        })),
23038        ..Default::default()
23039    };
23040    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
23041
23042    cx.set_state(indoc! {"
23043        struct Fˇoo {}
23044    "});
23045
23046    cx.update_editor(|editor, _, cx| {
23047        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
23048        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
23049        editor.highlight_background::<DocumentHighlightRead>(
23050            &[highlight_range],
23051            |theme| theme.colors().editor_document_highlight_read_background,
23052            cx,
23053        );
23054    });
23055
23056    let mut prepare_rename_handler = cx
23057        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
23058            move |_, _, _| async move {
23059                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
23060                    start: lsp::Position {
23061                        line: 0,
23062                        character: 7,
23063                    },
23064                    end: lsp::Position {
23065                        line: 0,
23066                        character: 10,
23067                    },
23068                })))
23069            },
23070        );
23071    let prepare_rename_task = cx
23072        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23073        .expect("Prepare rename was not started");
23074    prepare_rename_handler.next().await.unwrap();
23075    prepare_rename_task.await.expect("Prepare rename failed");
23076
23077    let mut rename_handler =
23078        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23079            let edit = lsp::TextEdit {
23080                range: lsp::Range {
23081                    start: lsp::Position {
23082                        line: 0,
23083                        character: 7,
23084                    },
23085                    end: lsp::Position {
23086                        line: 0,
23087                        character: 10,
23088                    },
23089                },
23090                new_text: "FooRenamed".to_string(),
23091            };
23092            Ok(Some(lsp::WorkspaceEdit::new(
23093                // Specify the same edit twice
23094                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
23095            )))
23096        });
23097    let rename_task = cx
23098        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23099        .expect("Confirm rename was not started");
23100    rename_handler.next().await.unwrap();
23101    rename_task.await.expect("Confirm rename failed");
23102    cx.run_until_parked();
23103
23104    // Despite two edits, only one is actually applied as those are identical
23105    cx.assert_editor_state(indoc! {"
23106        struct FooRenamedˇ {}
23107    "});
23108}
23109
23110#[gpui::test]
23111async fn test_rename_without_prepare(cx: &mut TestAppContext) {
23112    init_test(cx, |_| {});
23113    // These capabilities indicate that the server does not support prepare rename.
23114    let capabilities = lsp::ServerCapabilities {
23115        rename_provider: Some(lsp::OneOf::Left(true)),
23116        ..Default::default()
23117    };
23118    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
23119
23120    cx.set_state(indoc! {"
23121        struct Fˇoo {}
23122    "});
23123
23124    cx.update_editor(|editor, _window, cx| {
23125        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
23126        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
23127        editor.highlight_background::<DocumentHighlightRead>(
23128            &[highlight_range],
23129            |theme| theme.colors().editor_document_highlight_read_background,
23130            cx,
23131        );
23132    });
23133
23134    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23135        .expect("Prepare rename was not started")
23136        .await
23137        .expect("Prepare rename failed");
23138
23139    let mut rename_handler =
23140        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23141            let edit = lsp::TextEdit {
23142                range: lsp::Range {
23143                    start: lsp::Position {
23144                        line: 0,
23145                        character: 7,
23146                    },
23147                    end: lsp::Position {
23148                        line: 0,
23149                        character: 10,
23150                    },
23151                },
23152                new_text: "FooRenamed".to_string(),
23153            };
23154            Ok(Some(lsp::WorkspaceEdit::new(
23155                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
23156            )))
23157        });
23158    let rename_task = cx
23159        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23160        .expect("Confirm rename was not started");
23161    rename_handler.next().await.unwrap();
23162    rename_task.await.expect("Confirm rename failed");
23163    cx.run_until_parked();
23164
23165    // Correct range is renamed, as `surrounding_word` is used to find it.
23166    cx.assert_editor_state(indoc! {"
23167        struct FooRenamedˇ {}
23168    "});
23169}
23170
23171#[gpui::test]
23172async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
23173    init_test(cx, |_| {});
23174    let mut cx = EditorTestContext::new(cx).await;
23175
23176    let language = Arc::new(
23177        Language::new(
23178            LanguageConfig::default(),
23179            Some(tree_sitter_html::LANGUAGE.into()),
23180        )
23181        .with_brackets_query(
23182            r#"
23183            ("<" @open "/>" @close)
23184            ("</" @open ">" @close)
23185            ("<" @open ">" @close)
23186            ("\"" @open "\"" @close)
23187            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
23188        "#,
23189        )
23190        .unwrap(),
23191    );
23192    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23193
23194    cx.set_state(indoc! {"
23195        <span>ˇ</span>
23196    "});
23197    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23198    cx.assert_editor_state(indoc! {"
23199        <span>
23200        ˇ
23201        </span>
23202    "});
23203
23204    cx.set_state(indoc! {"
23205        <span><span></span>ˇ</span>
23206    "});
23207    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23208    cx.assert_editor_state(indoc! {"
23209        <span><span></span>
23210        ˇ</span>
23211    "});
23212
23213    cx.set_state(indoc! {"
23214        <span>ˇ
23215        </span>
23216    "});
23217    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23218    cx.assert_editor_state(indoc! {"
23219        <span>
23220        ˇ
23221        </span>
23222    "});
23223}
23224
23225#[gpui::test(iterations = 10)]
23226async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
23227    init_test(cx, |_| {});
23228
23229    let fs = FakeFs::new(cx.executor());
23230    fs.insert_tree(
23231        path!("/dir"),
23232        json!({
23233            "a.ts": "a",
23234        }),
23235    )
23236    .await;
23237
23238    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
23239    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23240    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23241
23242    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23243    language_registry.add(Arc::new(Language::new(
23244        LanguageConfig {
23245            name: "TypeScript".into(),
23246            matcher: LanguageMatcher {
23247                path_suffixes: vec!["ts".to_string()],
23248                ..Default::default()
23249            },
23250            ..Default::default()
23251        },
23252        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
23253    )));
23254    let mut fake_language_servers = language_registry.register_fake_lsp(
23255        "TypeScript",
23256        FakeLspAdapter {
23257            capabilities: lsp::ServerCapabilities {
23258                code_lens_provider: Some(lsp::CodeLensOptions {
23259                    resolve_provider: Some(true),
23260                }),
23261                execute_command_provider: Some(lsp::ExecuteCommandOptions {
23262                    commands: vec!["_the/command".to_string()],
23263                    ..lsp::ExecuteCommandOptions::default()
23264                }),
23265                ..lsp::ServerCapabilities::default()
23266            },
23267            ..FakeLspAdapter::default()
23268        },
23269    );
23270
23271    let editor = workspace
23272        .update(cx, |workspace, window, cx| {
23273            workspace.open_abs_path(
23274                PathBuf::from(path!("/dir/a.ts")),
23275                OpenOptions::default(),
23276                window,
23277                cx,
23278            )
23279        })
23280        .unwrap()
23281        .await
23282        .unwrap()
23283        .downcast::<Editor>()
23284        .unwrap();
23285    cx.executor().run_until_parked();
23286
23287    let fake_server = fake_language_servers.next().await.unwrap();
23288
23289    let buffer = editor.update(cx, |editor, cx| {
23290        editor
23291            .buffer()
23292            .read(cx)
23293            .as_singleton()
23294            .expect("have opened a single file by path")
23295    });
23296
23297    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
23298    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
23299    drop(buffer_snapshot);
23300    let actions = cx
23301        .update_window(*workspace, |_, window, cx| {
23302            project.code_actions(&buffer, anchor..anchor, window, cx)
23303        })
23304        .unwrap();
23305
23306    fake_server
23307        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23308            Ok(Some(vec![
23309                lsp::CodeLens {
23310                    range: lsp::Range::default(),
23311                    command: Some(lsp::Command {
23312                        title: "Code lens command".to_owned(),
23313                        command: "_the/command".to_owned(),
23314                        arguments: None,
23315                    }),
23316                    data: None,
23317                },
23318                lsp::CodeLens {
23319                    range: lsp::Range::default(),
23320                    command: Some(lsp::Command {
23321                        title: "Command not in capabilities".to_owned(),
23322                        command: "not in capabilities".to_owned(),
23323                        arguments: None,
23324                    }),
23325                    data: None,
23326                },
23327                lsp::CodeLens {
23328                    range: lsp::Range {
23329                        start: lsp::Position {
23330                            line: 1,
23331                            character: 1,
23332                        },
23333                        end: lsp::Position {
23334                            line: 1,
23335                            character: 1,
23336                        },
23337                    },
23338                    command: Some(lsp::Command {
23339                        title: "Command not in range".to_owned(),
23340                        command: "_the/command".to_owned(),
23341                        arguments: None,
23342                    }),
23343                    data: None,
23344                },
23345            ]))
23346        })
23347        .next()
23348        .await;
23349
23350    let actions = actions.await.unwrap();
23351    assert_eq!(
23352        actions.len(),
23353        1,
23354        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
23355    );
23356    let action = actions[0].clone();
23357    let apply = project.update(cx, |project, cx| {
23358        project.apply_code_action(buffer.clone(), action, true, cx)
23359    });
23360
23361    // Resolving the code action does not populate its edits. In absence of
23362    // edits, we must execute the given command.
23363    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
23364        |mut lens, _| async move {
23365            let lens_command = lens.command.as_mut().expect("should have a command");
23366            assert_eq!(lens_command.title, "Code lens command");
23367            lens_command.arguments = Some(vec![json!("the-argument")]);
23368            Ok(lens)
23369        },
23370    );
23371
23372    // While executing the command, the language server sends the editor
23373    // a `workspaceEdit` request.
23374    fake_server
23375        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
23376            let fake = fake_server.clone();
23377            move |params, _| {
23378                assert_eq!(params.command, "_the/command");
23379                let fake = fake.clone();
23380                async move {
23381                    fake.server
23382                        .request::<lsp::request::ApplyWorkspaceEdit>(
23383                            lsp::ApplyWorkspaceEditParams {
23384                                label: None,
23385                                edit: lsp::WorkspaceEdit {
23386                                    changes: Some(
23387                                        [(
23388                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
23389                                            vec![lsp::TextEdit {
23390                                                range: lsp::Range::new(
23391                                                    lsp::Position::new(0, 0),
23392                                                    lsp::Position::new(0, 0),
23393                                                ),
23394                                                new_text: "X".into(),
23395                                            }],
23396                                        )]
23397                                        .into_iter()
23398                                        .collect(),
23399                                    ),
23400                                    ..lsp::WorkspaceEdit::default()
23401                                },
23402                            },
23403                        )
23404                        .await
23405                        .into_response()
23406                        .unwrap();
23407                    Ok(Some(json!(null)))
23408                }
23409            }
23410        })
23411        .next()
23412        .await;
23413
23414    // Applying the code lens command returns a project transaction containing the edits
23415    // sent by the language server in its `workspaceEdit` request.
23416    let transaction = apply.await.unwrap();
23417    assert!(transaction.0.contains_key(&buffer));
23418    buffer.update(cx, |buffer, cx| {
23419        assert_eq!(buffer.text(), "Xa");
23420        buffer.undo(cx);
23421        assert_eq!(buffer.text(), "a");
23422    });
23423
23424    let actions_after_edits = cx
23425        .update_window(*workspace, |_, window, cx| {
23426            project.code_actions(&buffer, anchor..anchor, window, cx)
23427        })
23428        .unwrap()
23429        .await
23430        .unwrap();
23431    assert_eq!(
23432        actions, actions_after_edits,
23433        "For the same selection, same code lens actions should be returned"
23434    );
23435
23436    let _responses =
23437        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23438            panic!("No more code lens requests are expected");
23439        });
23440    editor.update_in(cx, |editor, window, cx| {
23441        editor.select_all(&SelectAll, window, cx);
23442    });
23443    cx.executor().run_until_parked();
23444    let new_actions = cx
23445        .update_window(*workspace, |_, window, cx| {
23446            project.code_actions(&buffer, anchor..anchor, window, cx)
23447        })
23448        .unwrap()
23449        .await
23450        .unwrap();
23451    assert_eq!(
23452        actions, new_actions,
23453        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
23454    );
23455}
23456
23457#[gpui::test]
23458async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
23459    init_test(cx, |_| {});
23460
23461    let fs = FakeFs::new(cx.executor());
23462    let main_text = r#"fn main() {
23463println!("1");
23464println!("2");
23465println!("3");
23466println!("4");
23467println!("5");
23468}"#;
23469    let lib_text = "mod foo {}";
23470    fs.insert_tree(
23471        path!("/a"),
23472        json!({
23473            "lib.rs": lib_text,
23474            "main.rs": main_text,
23475        }),
23476    )
23477    .await;
23478
23479    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23480    let (workspace, cx) =
23481        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23482    let worktree_id = workspace.update(cx, |workspace, cx| {
23483        workspace.project().update(cx, |project, cx| {
23484            project.worktrees(cx).next().unwrap().read(cx).id()
23485        })
23486    });
23487
23488    let expected_ranges = vec![
23489        Point::new(0, 0)..Point::new(0, 0),
23490        Point::new(1, 0)..Point::new(1, 1),
23491        Point::new(2, 0)..Point::new(2, 2),
23492        Point::new(3, 0)..Point::new(3, 3),
23493    ];
23494
23495    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23496    let editor_1 = workspace
23497        .update_in(cx, |workspace, window, cx| {
23498            workspace.open_path(
23499                (worktree_id, rel_path("main.rs")),
23500                Some(pane_1.downgrade()),
23501                true,
23502                window,
23503                cx,
23504            )
23505        })
23506        .unwrap()
23507        .await
23508        .downcast::<Editor>()
23509        .unwrap();
23510    pane_1.update(cx, |pane, cx| {
23511        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23512        open_editor.update(cx, |editor, cx| {
23513            assert_eq!(
23514                editor.display_text(cx),
23515                main_text,
23516                "Original main.rs text on initial open",
23517            );
23518            assert_eq!(
23519                editor
23520                    .selections
23521                    .all::<Point>(cx)
23522                    .into_iter()
23523                    .map(|s| s.range())
23524                    .collect::<Vec<_>>(),
23525                vec![Point::zero()..Point::zero()],
23526                "Default selections on initial open",
23527            );
23528        })
23529    });
23530    editor_1.update_in(cx, |editor, window, cx| {
23531        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23532            s.select_ranges(expected_ranges.clone());
23533        });
23534    });
23535
23536    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
23537        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
23538    });
23539    let editor_2 = workspace
23540        .update_in(cx, |workspace, window, cx| {
23541            workspace.open_path(
23542                (worktree_id, rel_path("main.rs")),
23543                Some(pane_2.downgrade()),
23544                true,
23545                window,
23546                cx,
23547            )
23548        })
23549        .unwrap()
23550        .await
23551        .downcast::<Editor>()
23552        .unwrap();
23553    pane_2.update(cx, |pane, cx| {
23554        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23555        open_editor.update(cx, |editor, cx| {
23556            assert_eq!(
23557                editor.display_text(cx),
23558                main_text,
23559                "Original main.rs text on initial open in another panel",
23560            );
23561            assert_eq!(
23562                editor
23563                    .selections
23564                    .all::<Point>(cx)
23565                    .into_iter()
23566                    .map(|s| s.range())
23567                    .collect::<Vec<_>>(),
23568                vec![Point::zero()..Point::zero()],
23569                "Default selections on initial open in another panel",
23570            );
23571        })
23572    });
23573
23574    editor_2.update_in(cx, |editor, window, cx| {
23575        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
23576    });
23577
23578    let _other_editor_1 = workspace
23579        .update_in(cx, |workspace, window, cx| {
23580            workspace.open_path(
23581                (worktree_id, rel_path("lib.rs")),
23582                Some(pane_1.downgrade()),
23583                true,
23584                window,
23585                cx,
23586            )
23587        })
23588        .unwrap()
23589        .await
23590        .downcast::<Editor>()
23591        .unwrap();
23592    pane_1
23593        .update_in(cx, |pane, window, cx| {
23594            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23595        })
23596        .await
23597        .unwrap();
23598    drop(editor_1);
23599    pane_1.update(cx, |pane, cx| {
23600        pane.active_item()
23601            .unwrap()
23602            .downcast::<Editor>()
23603            .unwrap()
23604            .update(cx, |editor, cx| {
23605                assert_eq!(
23606                    editor.display_text(cx),
23607                    lib_text,
23608                    "Other file should be open and active",
23609                );
23610            });
23611        assert_eq!(pane.items().count(), 1, "No other editors should be open");
23612    });
23613
23614    let _other_editor_2 = workspace
23615        .update_in(cx, |workspace, window, cx| {
23616            workspace.open_path(
23617                (worktree_id, rel_path("lib.rs")),
23618                Some(pane_2.downgrade()),
23619                true,
23620                window,
23621                cx,
23622            )
23623        })
23624        .unwrap()
23625        .await
23626        .downcast::<Editor>()
23627        .unwrap();
23628    pane_2
23629        .update_in(cx, |pane, window, cx| {
23630            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23631        })
23632        .await
23633        .unwrap();
23634    drop(editor_2);
23635    pane_2.update(cx, |pane, cx| {
23636        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23637        open_editor.update(cx, |editor, cx| {
23638            assert_eq!(
23639                editor.display_text(cx),
23640                lib_text,
23641                "Other file should be open and active in another panel too",
23642            );
23643        });
23644        assert_eq!(
23645            pane.items().count(),
23646            1,
23647            "No other editors should be open in another pane",
23648        );
23649    });
23650
23651    let _editor_1_reopened = workspace
23652        .update_in(cx, |workspace, window, cx| {
23653            workspace.open_path(
23654                (worktree_id, rel_path("main.rs")),
23655                Some(pane_1.downgrade()),
23656                true,
23657                window,
23658                cx,
23659            )
23660        })
23661        .unwrap()
23662        .await
23663        .downcast::<Editor>()
23664        .unwrap();
23665    let _editor_2_reopened = workspace
23666        .update_in(cx, |workspace, window, cx| {
23667            workspace.open_path(
23668                (worktree_id, rel_path("main.rs")),
23669                Some(pane_2.downgrade()),
23670                true,
23671                window,
23672                cx,
23673            )
23674        })
23675        .unwrap()
23676        .await
23677        .downcast::<Editor>()
23678        .unwrap();
23679    pane_1.update(cx, |pane, cx| {
23680        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23681        open_editor.update(cx, |editor, cx| {
23682            assert_eq!(
23683                editor.display_text(cx),
23684                main_text,
23685                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
23686            );
23687            assert_eq!(
23688                editor
23689                    .selections
23690                    .all::<Point>(cx)
23691                    .into_iter()
23692                    .map(|s| s.range())
23693                    .collect::<Vec<_>>(),
23694                expected_ranges,
23695                "Previous editor in the 1st panel had selections and should get them restored on reopen",
23696            );
23697        })
23698    });
23699    pane_2.update(cx, |pane, cx| {
23700        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23701        open_editor.update(cx, |editor, cx| {
23702            assert_eq!(
23703                editor.display_text(cx),
23704                r#"fn main() {
23705⋯rintln!("1");
23706⋯intln!("2");
23707⋯ntln!("3");
23708println!("4");
23709println!("5");
23710}"#,
23711                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
23712            );
23713            assert_eq!(
23714                editor
23715                    .selections
23716                    .all::<Point>(cx)
23717                    .into_iter()
23718                    .map(|s| s.range())
23719                    .collect::<Vec<_>>(),
23720                vec![Point::zero()..Point::zero()],
23721                "Previous editor in the 2nd pane had no selections changed hence should restore none",
23722            );
23723        })
23724    });
23725}
23726
23727#[gpui::test]
23728async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
23729    init_test(cx, |_| {});
23730
23731    let fs = FakeFs::new(cx.executor());
23732    let main_text = r#"fn main() {
23733println!("1");
23734println!("2");
23735println!("3");
23736println!("4");
23737println!("5");
23738}"#;
23739    let lib_text = "mod foo {}";
23740    fs.insert_tree(
23741        path!("/a"),
23742        json!({
23743            "lib.rs": lib_text,
23744            "main.rs": main_text,
23745        }),
23746    )
23747    .await;
23748
23749    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23750    let (workspace, cx) =
23751        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23752    let worktree_id = workspace.update(cx, |workspace, cx| {
23753        workspace.project().update(cx, |project, cx| {
23754            project.worktrees(cx).next().unwrap().read(cx).id()
23755        })
23756    });
23757
23758    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23759    let editor = workspace
23760        .update_in(cx, |workspace, window, cx| {
23761            workspace.open_path(
23762                (worktree_id, rel_path("main.rs")),
23763                Some(pane.downgrade()),
23764                true,
23765                window,
23766                cx,
23767            )
23768        })
23769        .unwrap()
23770        .await
23771        .downcast::<Editor>()
23772        .unwrap();
23773    pane.update(cx, |pane, cx| {
23774        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23775        open_editor.update(cx, |editor, cx| {
23776            assert_eq!(
23777                editor.display_text(cx),
23778                main_text,
23779                "Original main.rs text on initial open",
23780            );
23781        })
23782    });
23783    editor.update_in(cx, |editor, window, cx| {
23784        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
23785    });
23786
23787    cx.update_global(|store: &mut SettingsStore, cx| {
23788        store.update_user_settings(cx, |s| {
23789            s.workspace.restore_on_file_reopen = Some(false);
23790        });
23791    });
23792    editor.update_in(cx, |editor, window, cx| {
23793        editor.fold_ranges(
23794            vec![
23795                Point::new(1, 0)..Point::new(1, 1),
23796                Point::new(2, 0)..Point::new(2, 2),
23797                Point::new(3, 0)..Point::new(3, 3),
23798            ],
23799            false,
23800            window,
23801            cx,
23802        );
23803    });
23804    pane.update_in(cx, |pane, window, cx| {
23805        pane.close_all_items(&CloseAllItems::default(), window, cx)
23806    })
23807    .await
23808    .unwrap();
23809    pane.update(cx, |pane, _| {
23810        assert!(pane.active_item().is_none());
23811    });
23812    cx.update_global(|store: &mut SettingsStore, cx| {
23813        store.update_user_settings(cx, |s| {
23814            s.workspace.restore_on_file_reopen = Some(true);
23815        });
23816    });
23817
23818    let _editor_reopened = workspace
23819        .update_in(cx, |workspace, window, cx| {
23820            workspace.open_path(
23821                (worktree_id, rel_path("main.rs")),
23822                Some(pane.downgrade()),
23823                true,
23824                window,
23825                cx,
23826            )
23827        })
23828        .unwrap()
23829        .await
23830        .downcast::<Editor>()
23831        .unwrap();
23832    pane.update(cx, |pane, cx| {
23833        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23834        open_editor.update(cx, |editor, cx| {
23835            assert_eq!(
23836                editor.display_text(cx),
23837                main_text,
23838                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
23839            );
23840        })
23841    });
23842}
23843
23844#[gpui::test]
23845async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
23846    struct EmptyModalView {
23847        focus_handle: gpui::FocusHandle,
23848    }
23849    impl EventEmitter<DismissEvent> for EmptyModalView {}
23850    impl Render for EmptyModalView {
23851        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
23852            div()
23853        }
23854    }
23855    impl Focusable for EmptyModalView {
23856        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
23857            self.focus_handle.clone()
23858        }
23859    }
23860    impl workspace::ModalView for EmptyModalView {}
23861    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
23862        EmptyModalView {
23863            focus_handle: cx.focus_handle(),
23864        }
23865    }
23866
23867    init_test(cx, |_| {});
23868
23869    let fs = FakeFs::new(cx.executor());
23870    let project = Project::test(fs, [], cx).await;
23871    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23872    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
23873    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23874    let editor = cx.new_window_entity(|window, cx| {
23875        Editor::new(
23876            EditorMode::full(),
23877            buffer,
23878            Some(project.clone()),
23879            window,
23880            cx,
23881        )
23882    });
23883    workspace
23884        .update(cx, |workspace, window, cx| {
23885            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
23886        })
23887        .unwrap();
23888    editor.update_in(cx, |editor, window, cx| {
23889        editor.open_context_menu(&OpenContextMenu, window, cx);
23890        assert!(editor.mouse_context_menu.is_some());
23891    });
23892    workspace
23893        .update(cx, |workspace, window, cx| {
23894            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
23895        })
23896        .unwrap();
23897    cx.read(|cx| {
23898        assert!(editor.read(cx).mouse_context_menu.is_none());
23899    });
23900}
23901
23902fn set_linked_edit_ranges(
23903    opening: (Point, Point),
23904    closing: (Point, Point),
23905    editor: &mut Editor,
23906    cx: &mut Context<Editor>,
23907) {
23908    let Some((buffer, _)) = editor
23909        .buffer
23910        .read(cx)
23911        .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
23912    else {
23913        panic!("Failed to get buffer for selection position");
23914    };
23915    let buffer = buffer.read(cx);
23916    let buffer_id = buffer.remote_id();
23917    let opening_range = buffer.anchor_before(opening.0)..buffer.anchor_after(opening.1);
23918    let closing_range = buffer.anchor_before(closing.0)..buffer.anchor_after(closing.1);
23919    let mut linked_ranges = HashMap::default();
23920    linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
23921    editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
23922}
23923
23924#[gpui::test]
23925async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
23926    init_test(cx, |_| {});
23927
23928    let fs = FakeFs::new(cx.executor());
23929    fs.insert_file(path!("/file.html"), Default::default())
23930        .await;
23931
23932    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
23933
23934    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23935    let html_language = Arc::new(Language::new(
23936        LanguageConfig {
23937            name: "HTML".into(),
23938            matcher: LanguageMatcher {
23939                path_suffixes: vec!["html".to_string()],
23940                ..LanguageMatcher::default()
23941            },
23942            brackets: BracketPairConfig {
23943                pairs: vec![BracketPair {
23944                    start: "<".into(),
23945                    end: ">".into(),
23946                    close: true,
23947                    ..Default::default()
23948                }],
23949                ..Default::default()
23950            },
23951            ..Default::default()
23952        },
23953        Some(tree_sitter_html::LANGUAGE.into()),
23954    ));
23955    language_registry.add(html_language);
23956    let mut fake_servers = language_registry.register_fake_lsp(
23957        "HTML",
23958        FakeLspAdapter {
23959            capabilities: lsp::ServerCapabilities {
23960                completion_provider: Some(lsp::CompletionOptions {
23961                    resolve_provider: Some(true),
23962                    ..Default::default()
23963                }),
23964                ..Default::default()
23965            },
23966            ..Default::default()
23967        },
23968    );
23969
23970    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23971    let cx = &mut VisualTestContext::from_window(*workspace, cx);
23972
23973    let worktree_id = workspace
23974        .update(cx, |workspace, _window, cx| {
23975            workspace.project().update(cx, |project, cx| {
23976                project.worktrees(cx).next().unwrap().read(cx).id()
23977            })
23978        })
23979        .unwrap();
23980    project
23981        .update(cx, |project, cx| {
23982            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
23983        })
23984        .await
23985        .unwrap();
23986    let editor = workspace
23987        .update(cx, |workspace, window, cx| {
23988            workspace.open_path((worktree_id, rel_path("file.html")), None, true, window, cx)
23989        })
23990        .unwrap()
23991        .await
23992        .unwrap()
23993        .downcast::<Editor>()
23994        .unwrap();
23995
23996    let fake_server = fake_servers.next().await.unwrap();
23997    editor.update_in(cx, |editor, window, cx| {
23998        editor.set_text("<ad></ad>", window, cx);
23999        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24000            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
24001        });
24002        set_linked_edit_ranges(
24003            (Point::new(0, 1), Point::new(0, 3)),
24004            (Point::new(0, 6), Point::new(0, 8)),
24005            editor,
24006            cx,
24007        );
24008    });
24009    let mut completion_handle =
24010        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
24011            Ok(Some(lsp::CompletionResponse::Array(vec![
24012                lsp::CompletionItem {
24013                    label: "head".to_string(),
24014                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
24015                        lsp::InsertReplaceEdit {
24016                            new_text: "head".to_string(),
24017                            insert: lsp::Range::new(
24018                                lsp::Position::new(0, 1),
24019                                lsp::Position::new(0, 3),
24020                            ),
24021                            replace: lsp::Range::new(
24022                                lsp::Position::new(0, 1),
24023                                lsp::Position::new(0, 3),
24024                            ),
24025                        },
24026                    )),
24027                    ..Default::default()
24028                },
24029            ])))
24030        });
24031    editor.update_in(cx, |editor, window, cx| {
24032        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
24033    });
24034    cx.run_until_parked();
24035    completion_handle.next().await.unwrap();
24036    editor.update(cx, |editor, _| {
24037        assert!(
24038            editor.context_menu_visible(),
24039            "Completion menu should be visible"
24040        );
24041    });
24042    editor.update_in(cx, |editor, window, cx| {
24043        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
24044    });
24045    cx.executor().run_until_parked();
24046    editor.update(cx, |editor, cx| {
24047        assert_eq!(editor.text(cx), "<head></head>");
24048    });
24049}
24050
24051#[gpui::test]
24052async fn test_linked_edits_on_typing_punctuation(cx: &mut TestAppContext) {
24053    init_test(cx, |_| {});
24054
24055    let mut cx = EditorTestContext::new(cx).await;
24056    let language = Arc::new(Language::new(
24057        LanguageConfig {
24058            name: "TSX".into(),
24059            matcher: LanguageMatcher {
24060                path_suffixes: vec!["tsx".to_string()],
24061                ..LanguageMatcher::default()
24062            },
24063            brackets: BracketPairConfig {
24064                pairs: vec![BracketPair {
24065                    start: "<".into(),
24066                    end: ">".into(),
24067                    close: true,
24068                    ..Default::default()
24069                }],
24070                ..Default::default()
24071            },
24072            linked_edit_characters: HashSet::from_iter(['.']),
24073            ..Default::default()
24074        },
24075        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
24076    ));
24077    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24078
24079    // Test typing > does not extend linked pair
24080    cx.set_state("<divˇ<div></div>");
24081    cx.update_editor(|editor, _, cx| {
24082        set_linked_edit_ranges(
24083            (Point::new(0, 1), Point::new(0, 4)),
24084            (Point::new(0, 11), Point::new(0, 14)),
24085            editor,
24086            cx,
24087        );
24088    });
24089    cx.update_editor(|editor, window, cx| {
24090        editor.handle_input(">", window, cx);
24091    });
24092    cx.assert_editor_state("<div>ˇ<div></div>");
24093
24094    // Test typing . do extend linked pair
24095    cx.set_state("<Animatedˇ></Animated>");
24096    cx.update_editor(|editor, _, cx| {
24097        set_linked_edit_ranges(
24098            (Point::new(0, 1), Point::new(0, 9)),
24099            (Point::new(0, 12), Point::new(0, 20)),
24100            editor,
24101            cx,
24102        );
24103    });
24104    cx.update_editor(|editor, window, cx| {
24105        editor.handle_input(".", window, cx);
24106    });
24107    cx.assert_editor_state("<Animated.ˇ></Animated.>");
24108    cx.update_editor(|editor, _, cx| {
24109        set_linked_edit_ranges(
24110            (Point::new(0, 1), Point::new(0, 10)),
24111            (Point::new(0, 13), Point::new(0, 21)),
24112            editor,
24113            cx,
24114        );
24115    });
24116    cx.update_editor(|editor, window, cx| {
24117        editor.handle_input("V", window, cx);
24118    });
24119    cx.assert_editor_state("<Animated.Vˇ></Animated.V>");
24120}
24121
24122#[gpui::test]
24123async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
24124    init_test(cx, |_| {});
24125
24126    let fs = FakeFs::new(cx.executor());
24127    fs.insert_tree(
24128        path!("/root"),
24129        json!({
24130            "a": {
24131                "main.rs": "fn main() {}",
24132            },
24133            "foo": {
24134                "bar": {
24135                    "external_file.rs": "pub mod external {}",
24136                }
24137            }
24138        }),
24139    )
24140    .await;
24141
24142    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
24143    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24144    language_registry.add(rust_lang());
24145    let _fake_servers = language_registry.register_fake_lsp(
24146        "Rust",
24147        FakeLspAdapter {
24148            ..FakeLspAdapter::default()
24149        },
24150    );
24151    let (workspace, cx) =
24152        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24153    let worktree_id = workspace.update(cx, |workspace, cx| {
24154        workspace.project().update(cx, |project, cx| {
24155            project.worktrees(cx).next().unwrap().read(cx).id()
24156        })
24157    });
24158
24159    let assert_language_servers_count =
24160        |expected: usize, context: &str, cx: &mut VisualTestContext| {
24161            project.update(cx, |project, cx| {
24162                let current = project
24163                    .lsp_store()
24164                    .read(cx)
24165                    .as_local()
24166                    .unwrap()
24167                    .language_servers
24168                    .len();
24169                assert_eq!(expected, current, "{context}");
24170            });
24171        };
24172
24173    assert_language_servers_count(
24174        0,
24175        "No servers should be running before any file is open",
24176        cx,
24177    );
24178    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24179    let main_editor = workspace
24180        .update_in(cx, |workspace, window, cx| {
24181            workspace.open_path(
24182                (worktree_id, rel_path("main.rs")),
24183                Some(pane.downgrade()),
24184                true,
24185                window,
24186                cx,
24187            )
24188        })
24189        .unwrap()
24190        .await
24191        .downcast::<Editor>()
24192        .unwrap();
24193    pane.update(cx, |pane, cx| {
24194        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24195        open_editor.update(cx, |editor, cx| {
24196            assert_eq!(
24197                editor.display_text(cx),
24198                "fn main() {}",
24199                "Original main.rs text on initial open",
24200            );
24201        });
24202        assert_eq!(open_editor, main_editor);
24203    });
24204    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
24205
24206    let external_editor = workspace
24207        .update_in(cx, |workspace, window, cx| {
24208            workspace.open_abs_path(
24209                PathBuf::from("/root/foo/bar/external_file.rs"),
24210                OpenOptions::default(),
24211                window,
24212                cx,
24213            )
24214        })
24215        .await
24216        .expect("opening external file")
24217        .downcast::<Editor>()
24218        .expect("downcasted external file's open element to editor");
24219    pane.update(cx, |pane, cx| {
24220        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24221        open_editor.update(cx, |editor, cx| {
24222            assert_eq!(
24223                editor.display_text(cx),
24224                "pub mod external {}",
24225                "External file is open now",
24226            );
24227        });
24228        assert_eq!(open_editor, external_editor);
24229    });
24230    assert_language_servers_count(
24231        1,
24232        "Second, external, *.rs file should join the existing server",
24233        cx,
24234    );
24235
24236    pane.update_in(cx, |pane, window, cx| {
24237        pane.close_active_item(&CloseActiveItem::default(), window, cx)
24238    })
24239    .await
24240    .unwrap();
24241    pane.update_in(cx, |pane, window, cx| {
24242        pane.navigate_backward(&Default::default(), window, cx);
24243    });
24244    cx.run_until_parked();
24245    pane.update(cx, |pane, cx| {
24246        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24247        open_editor.update(cx, |editor, cx| {
24248            assert_eq!(
24249                editor.display_text(cx),
24250                "pub mod external {}",
24251                "External file is open now",
24252            );
24253        });
24254    });
24255    assert_language_servers_count(
24256        1,
24257        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
24258        cx,
24259    );
24260
24261    cx.update(|_, cx| {
24262        workspace::reload(cx);
24263    });
24264    assert_language_servers_count(
24265        1,
24266        "After reloading the worktree with local and external files opened, only one project should be started",
24267        cx,
24268    );
24269}
24270
24271#[gpui::test]
24272async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
24273    init_test(cx, |_| {});
24274
24275    let mut cx = EditorTestContext::new(cx).await;
24276    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24277    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24278
24279    // test cursor move to start of each line on tab
24280    // for `if`, `elif`, `else`, `while`, `with` and `for`
24281    cx.set_state(indoc! {"
24282        def main():
24283        ˇ    for item in items:
24284        ˇ        while item.active:
24285        ˇ            if item.value > 10:
24286        ˇ                continue
24287        ˇ            elif item.value < 0:
24288        ˇ                break
24289        ˇ            else:
24290        ˇ                with item.context() as ctx:
24291        ˇ                    yield count
24292        ˇ        else:
24293        ˇ            log('while else')
24294        ˇ    else:
24295        ˇ        log('for else')
24296    "});
24297    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24298    cx.assert_editor_state(indoc! {"
24299        def main():
24300            ˇfor item in items:
24301                ˇwhile item.active:
24302                    ˇif item.value > 10:
24303                        ˇcontinue
24304                    ˇelif item.value < 0:
24305                        ˇbreak
24306                    ˇelse:
24307                        ˇwith item.context() as ctx:
24308                            ˇyield count
24309                ˇelse:
24310                    ˇlog('while else')
24311            ˇelse:
24312                ˇlog('for else')
24313    "});
24314    // test relative indent is preserved when tab
24315    // for `if`, `elif`, `else`, `while`, `with` and `for`
24316    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24317    cx.assert_editor_state(indoc! {"
24318        def main():
24319                ˇfor item in items:
24320                    ˇwhile item.active:
24321                        ˇif item.value > 10:
24322                            ˇcontinue
24323                        ˇelif item.value < 0:
24324                            ˇbreak
24325                        ˇelse:
24326                            ˇwith item.context() as ctx:
24327                                ˇyield count
24328                    ˇelse:
24329                        ˇlog('while else')
24330                ˇelse:
24331                    ˇlog('for else')
24332    "});
24333
24334    // test cursor move to start of each line on tab
24335    // for `try`, `except`, `else`, `finally`, `match` and `def`
24336    cx.set_state(indoc! {"
24337        def main():
24338        ˇ    try:
24339        ˇ        fetch()
24340        ˇ    except ValueError:
24341        ˇ        handle_error()
24342        ˇ    else:
24343        ˇ        match value:
24344        ˇ            case _:
24345        ˇ    finally:
24346        ˇ        def status():
24347        ˇ            return 0
24348    "});
24349    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24350    cx.assert_editor_state(indoc! {"
24351        def main():
24352            ˇtry:
24353                ˇfetch()
24354            ˇexcept ValueError:
24355                ˇhandle_error()
24356            ˇelse:
24357                ˇmatch value:
24358                    ˇcase _:
24359            ˇfinally:
24360                ˇdef status():
24361                    ˇreturn 0
24362    "});
24363    // test relative indent is preserved when tab
24364    // for `try`, `except`, `else`, `finally`, `match` and `def`
24365    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24366    cx.assert_editor_state(indoc! {"
24367        def main():
24368                ˇtry:
24369                    ˇfetch()
24370                ˇexcept ValueError:
24371                    ˇhandle_error()
24372                ˇelse:
24373                    ˇmatch value:
24374                        ˇcase _:
24375                ˇfinally:
24376                    ˇdef status():
24377                        ˇreturn 0
24378    "});
24379}
24380
24381#[gpui::test]
24382async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
24383    init_test(cx, |_| {});
24384
24385    let mut cx = EditorTestContext::new(cx).await;
24386    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24387    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24388
24389    // test `else` auto outdents when typed inside `if` block
24390    cx.set_state(indoc! {"
24391        def main():
24392            if i == 2:
24393                return
24394                ˇ
24395    "});
24396    cx.update_editor(|editor, window, cx| {
24397        editor.handle_input("else:", window, cx);
24398    });
24399    cx.assert_editor_state(indoc! {"
24400        def main():
24401            if i == 2:
24402                return
24403            else:ˇ
24404    "});
24405
24406    // test `except` auto outdents when typed inside `try` block
24407    cx.set_state(indoc! {"
24408        def main():
24409            try:
24410                i = 2
24411                ˇ
24412    "});
24413    cx.update_editor(|editor, window, cx| {
24414        editor.handle_input("except:", window, cx);
24415    });
24416    cx.assert_editor_state(indoc! {"
24417        def main():
24418            try:
24419                i = 2
24420            except:ˇ
24421    "});
24422
24423    // test `else` auto outdents when typed inside `except` block
24424    cx.set_state(indoc! {"
24425        def main():
24426            try:
24427                i = 2
24428            except:
24429                j = 2
24430                ˇ
24431    "});
24432    cx.update_editor(|editor, window, cx| {
24433        editor.handle_input("else:", window, cx);
24434    });
24435    cx.assert_editor_state(indoc! {"
24436        def main():
24437            try:
24438                i = 2
24439            except:
24440                j = 2
24441            else:ˇ
24442    "});
24443
24444    // test `finally` auto outdents when typed inside `else` block
24445    cx.set_state(indoc! {"
24446        def main():
24447            try:
24448                i = 2
24449            except:
24450                j = 2
24451            else:
24452                k = 2
24453                ˇ
24454    "});
24455    cx.update_editor(|editor, window, cx| {
24456        editor.handle_input("finally:", window, cx);
24457    });
24458    cx.assert_editor_state(indoc! {"
24459        def main():
24460            try:
24461                i = 2
24462            except:
24463                j = 2
24464            else:
24465                k = 2
24466            finally:ˇ
24467    "});
24468
24469    // test `else` does not outdents when typed inside `except` block right after for block
24470    cx.set_state(indoc! {"
24471        def main():
24472            try:
24473                i = 2
24474            except:
24475                for i in range(n):
24476                    pass
24477                ˇ
24478    "});
24479    cx.update_editor(|editor, window, cx| {
24480        editor.handle_input("else:", window, cx);
24481    });
24482    cx.assert_editor_state(indoc! {"
24483        def main():
24484            try:
24485                i = 2
24486            except:
24487                for i in range(n):
24488                    pass
24489                else:ˇ
24490    "});
24491
24492    // test `finally` auto outdents when typed inside `else` block right after for block
24493    cx.set_state(indoc! {"
24494        def main():
24495            try:
24496                i = 2
24497            except:
24498                j = 2
24499            else:
24500                for i in range(n):
24501                    pass
24502                ˇ
24503    "});
24504    cx.update_editor(|editor, window, cx| {
24505        editor.handle_input("finally:", window, cx);
24506    });
24507    cx.assert_editor_state(indoc! {"
24508        def main():
24509            try:
24510                i = 2
24511            except:
24512                j = 2
24513            else:
24514                for i in range(n):
24515                    pass
24516            finally:ˇ
24517    "});
24518
24519    // test `except` outdents to inner "try" block
24520    cx.set_state(indoc! {"
24521        def main():
24522            try:
24523                i = 2
24524                if i == 2:
24525                    try:
24526                        i = 3
24527                        ˇ
24528    "});
24529    cx.update_editor(|editor, window, cx| {
24530        editor.handle_input("except:", window, cx);
24531    });
24532    cx.assert_editor_state(indoc! {"
24533        def main():
24534            try:
24535                i = 2
24536                if i == 2:
24537                    try:
24538                        i = 3
24539                    except:ˇ
24540    "});
24541
24542    // test `except` outdents to outer "try" block
24543    cx.set_state(indoc! {"
24544        def main():
24545            try:
24546                i = 2
24547                if i == 2:
24548                    try:
24549                        i = 3
24550                ˇ
24551    "});
24552    cx.update_editor(|editor, window, cx| {
24553        editor.handle_input("except:", window, cx);
24554    });
24555    cx.assert_editor_state(indoc! {"
24556        def main():
24557            try:
24558                i = 2
24559                if i == 2:
24560                    try:
24561                        i = 3
24562            except:ˇ
24563    "});
24564
24565    // test `else` stays at correct indent when typed after `for` block
24566    cx.set_state(indoc! {"
24567        def main():
24568            for i in range(10):
24569                if i == 3:
24570                    break
24571            ˇ
24572    "});
24573    cx.update_editor(|editor, window, cx| {
24574        editor.handle_input("else:", window, cx);
24575    });
24576    cx.assert_editor_state(indoc! {"
24577        def main():
24578            for i in range(10):
24579                if i == 3:
24580                    break
24581            else:ˇ
24582    "});
24583
24584    // test does not outdent on typing after line with square brackets
24585    cx.set_state(indoc! {"
24586        def f() -> list[str]:
24587            ˇ
24588    "});
24589    cx.update_editor(|editor, window, cx| {
24590        editor.handle_input("a", window, cx);
24591    });
24592    cx.assert_editor_state(indoc! {"
24593        def f() -> list[str]:
2459424595    "});
24596
24597    // test does not outdent on typing : after case keyword
24598    cx.set_state(indoc! {"
24599        match 1:
24600            caseˇ
24601    "});
24602    cx.update_editor(|editor, window, cx| {
24603        editor.handle_input(":", window, cx);
24604    });
24605    cx.assert_editor_state(indoc! {"
24606        match 1:
24607            case:ˇ
24608    "});
24609}
24610
24611#[gpui::test]
24612async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
24613    init_test(cx, |_| {});
24614    update_test_language_settings(cx, |settings| {
24615        settings.defaults.extend_comment_on_newline = Some(false);
24616    });
24617    let mut cx = EditorTestContext::new(cx).await;
24618    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24619    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24620
24621    // test correct indent after newline on comment
24622    cx.set_state(indoc! {"
24623        # COMMENT:ˇ
24624    "});
24625    cx.update_editor(|editor, window, cx| {
24626        editor.newline(&Newline, window, cx);
24627    });
24628    cx.assert_editor_state(indoc! {"
24629        # COMMENT:
24630        ˇ
24631    "});
24632
24633    // test correct indent after newline in brackets
24634    cx.set_state(indoc! {"
24635        {ˇ}
24636    "});
24637    cx.update_editor(|editor, window, cx| {
24638        editor.newline(&Newline, window, cx);
24639    });
24640    cx.run_until_parked();
24641    cx.assert_editor_state(indoc! {"
24642        {
24643            ˇ
24644        }
24645    "});
24646
24647    cx.set_state(indoc! {"
24648        (ˇ)
24649    "});
24650    cx.update_editor(|editor, window, cx| {
24651        editor.newline(&Newline, window, cx);
24652    });
24653    cx.run_until_parked();
24654    cx.assert_editor_state(indoc! {"
24655        (
24656            ˇ
24657        )
24658    "});
24659
24660    // do not indent after empty lists or dictionaries
24661    cx.set_state(indoc! {"
24662        a = []ˇ
24663    "});
24664    cx.update_editor(|editor, window, cx| {
24665        editor.newline(&Newline, window, cx);
24666    });
24667    cx.run_until_parked();
24668    cx.assert_editor_state(indoc! {"
24669        a = []
24670        ˇ
24671    "});
24672}
24673
24674#[gpui::test]
24675async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
24676    init_test(cx, |_| {});
24677
24678    let mut cx = EditorTestContext::new(cx).await;
24679    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24680    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24681
24682    // test cursor move to start of each line on tab
24683    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
24684    cx.set_state(indoc! {"
24685        function main() {
24686        ˇ    for item in $items; do
24687        ˇ        while [ -n \"$item\" ]; do
24688        ˇ            if [ \"$value\" -gt 10 ]; then
24689        ˇ                continue
24690        ˇ            elif [ \"$value\" -lt 0 ]; then
24691        ˇ                break
24692        ˇ            else
24693        ˇ                echo \"$item\"
24694        ˇ            fi
24695        ˇ        done
24696        ˇ    done
24697        ˇ}
24698    "});
24699    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24700    cx.assert_editor_state(indoc! {"
24701        function main() {
24702            ˇfor item in $items; do
24703                ˇwhile [ -n \"$item\" ]; do
24704                    ˇif [ \"$value\" -gt 10 ]; then
24705                        ˇcontinue
24706                    ˇelif [ \"$value\" -lt 0 ]; then
24707                        ˇbreak
24708                    ˇelse
24709                        ˇecho \"$item\"
24710                    ˇfi
24711                ˇdone
24712            ˇdone
24713        ˇ}
24714    "});
24715    // test relative indent is preserved when tab
24716    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24717    cx.assert_editor_state(indoc! {"
24718        function main() {
24719                ˇfor item in $items; do
24720                    ˇwhile [ -n \"$item\" ]; do
24721                        ˇif [ \"$value\" -gt 10 ]; then
24722                            ˇcontinue
24723                        ˇelif [ \"$value\" -lt 0 ]; then
24724                            ˇbreak
24725                        ˇelse
24726                            ˇecho \"$item\"
24727                        ˇfi
24728                    ˇdone
24729                ˇdone
24730            ˇ}
24731    "});
24732
24733    // test cursor move to start of each line on tab
24734    // for `case` statement with patterns
24735    cx.set_state(indoc! {"
24736        function handle() {
24737        ˇ    case \"$1\" in
24738        ˇ        start)
24739        ˇ            echo \"a\"
24740        ˇ            ;;
24741        ˇ        stop)
24742        ˇ            echo \"b\"
24743        ˇ            ;;
24744        ˇ        *)
24745        ˇ            echo \"c\"
24746        ˇ            ;;
24747        ˇ    esac
24748        ˇ}
24749    "});
24750    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24751    cx.assert_editor_state(indoc! {"
24752        function handle() {
24753            ˇcase \"$1\" in
24754                ˇstart)
24755                    ˇecho \"a\"
24756                    ˇ;;
24757                ˇstop)
24758                    ˇecho \"b\"
24759                    ˇ;;
24760                ˇ*)
24761                    ˇecho \"c\"
24762                    ˇ;;
24763            ˇesac
24764        ˇ}
24765    "});
24766}
24767
24768#[gpui::test]
24769async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
24770    init_test(cx, |_| {});
24771
24772    let mut cx = EditorTestContext::new(cx).await;
24773    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24774    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24775
24776    // test indents on comment insert
24777    cx.set_state(indoc! {"
24778        function main() {
24779        ˇ    for item in $items; do
24780        ˇ        while [ -n \"$item\" ]; do
24781        ˇ            if [ \"$value\" -gt 10 ]; then
24782        ˇ                continue
24783        ˇ            elif [ \"$value\" -lt 0 ]; then
24784        ˇ                break
24785        ˇ            else
24786        ˇ                echo \"$item\"
24787        ˇ            fi
24788        ˇ        done
24789        ˇ    done
24790        ˇ}
24791    "});
24792    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
24793    cx.assert_editor_state(indoc! {"
24794        function main() {
24795        #ˇ    for item in $items; do
24796        #ˇ        while [ -n \"$item\" ]; do
24797        #ˇ            if [ \"$value\" -gt 10 ]; then
24798        #ˇ                continue
24799        #ˇ            elif [ \"$value\" -lt 0 ]; then
24800        #ˇ                break
24801        #ˇ            else
24802        #ˇ                echo \"$item\"
24803        #ˇ            fi
24804        #ˇ        done
24805        #ˇ    done
24806        #ˇ}
24807    "});
24808}
24809
24810#[gpui::test]
24811async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
24812    init_test(cx, |_| {});
24813
24814    let mut cx = EditorTestContext::new(cx).await;
24815    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24816    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24817
24818    // test `else` auto outdents when typed inside `if` block
24819    cx.set_state(indoc! {"
24820        if [ \"$1\" = \"test\" ]; then
24821            echo \"foo bar\"
24822            ˇ
24823    "});
24824    cx.update_editor(|editor, window, cx| {
24825        editor.handle_input("else", window, cx);
24826    });
24827    cx.assert_editor_state(indoc! {"
24828        if [ \"$1\" = \"test\" ]; then
24829            echo \"foo bar\"
24830        elseˇ
24831    "});
24832
24833    // test `elif` auto outdents when typed inside `if` block
24834    cx.set_state(indoc! {"
24835        if [ \"$1\" = \"test\" ]; then
24836            echo \"foo bar\"
24837            ˇ
24838    "});
24839    cx.update_editor(|editor, window, cx| {
24840        editor.handle_input("elif", window, cx);
24841    });
24842    cx.assert_editor_state(indoc! {"
24843        if [ \"$1\" = \"test\" ]; then
24844            echo \"foo bar\"
24845        elifˇ
24846    "});
24847
24848    // test `fi` auto outdents when typed inside `else` block
24849    cx.set_state(indoc! {"
24850        if [ \"$1\" = \"test\" ]; then
24851            echo \"foo bar\"
24852        else
24853            echo \"bar baz\"
24854            ˇ
24855    "});
24856    cx.update_editor(|editor, window, cx| {
24857        editor.handle_input("fi", window, cx);
24858    });
24859    cx.assert_editor_state(indoc! {"
24860        if [ \"$1\" = \"test\" ]; then
24861            echo \"foo bar\"
24862        else
24863            echo \"bar baz\"
24864        fiˇ
24865    "});
24866
24867    // test `done` auto outdents when typed inside `while` block
24868    cx.set_state(indoc! {"
24869        while read line; do
24870            echo \"$line\"
24871            ˇ
24872    "});
24873    cx.update_editor(|editor, window, cx| {
24874        editor.handle_input("done", window, cx);
24875    });
24876    cx.assert_editor_state(indoc! {"
24877        while read line; do
24878            echo \"$line\"
24879        doneˇ
24880    "});
24881
24882    // test `done` auto outdents when typed inside `for` block
24883    cx.set_state(indoc! {"
24884        for file in *.txt; do
24885            cat \"$file\"
24886            ˇ
24887    "});
24888    cx.update_editor(|editor, window, cx| {
24889        editor.handle_input("done", window, cx);
24890    });
24891    cx.assert_editor_state(indoc! {"
24892        for file in *.txt; do
24893            cat \"$file\"
24894        doneˇ
24895    "});
24896
24897    // test `esac` auto outdents when typed inside `case` block
24898    cx.set_state(indoc! {"
24899        case \"$1\" in
24900            start)
24901                echo \"foo bar\"
24902                ;;
24903            stop)
24904                echo \"bar baz\"
24905                ;;
24906            ˇ
24907    "});
24908    cx.update_editor(|editor, window, cx| {
24909        editor.handle_input("esac", window, cx);
24910    });
24911    cx.assert_editor_state(indoc! {"
24912        case \"$1\" in
24913            start)
24914                echo \"foo bar\"
24915                ;;
24916            stop)
24917                echo \"bar baz\"
24918                ;;
24919        esacˇ
24920    "});
24921
24922    // test `*)` auto outdents when typed inside `case` block
24923    cx.set_state(indoc! {"
24924        case \"$1\" in
24925            start)
24926                echo \"foo bar\"
24927                ;;
24928                ˇ
24929    "});
24930    cx.update_editor(|editor, window, cx| {
24931        editor.handle_input("*)", window, cx);
24932    });
24933    cx.assert_editor_state(indoc! {"
24934        case \"$1\" in
24935            start)
24936                echo \"foo bar\"
24937                ;;
24938            *)ˇ
24939    "});
24940
24941    // test `fi` outdents to correct level with nested if blocks
24942    cx.set_state(indoc! {"
24943        if [ \"$1\" = \"test\" ]; then
24944            echo \"outer if\"
24945            if [ \"$2\" = \"debug\" ]; then
24946                echo \"inner if\"
24947                ˇ
24948    "});
24949    cx.update_editor(|editor, window, cx| {
24950        editor.handle_input("fi", window, cx);
24951    });
24952    cx.assert_editor_state(indoc! {"
24953        if [ \"$1\" = \"test\" ]; then
24954            echo \"outer if\"
24955            if [ \"$2\" = \"debug\" ]; then
24956                echo \"inner if\"
24957            fiˇ
24958    "});
24959}
24960
24961#[gpui::test]
24962async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
24963    init_test(cx, |_| {});
24964    update_test_language_settings(cx, |settings| {
24965        settings.defaults.extend_comment_on_newline = Some(false);
24966    });
24967    let mut cx = EditorTestContext::new(cx).await;
24968    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24969    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24970
24971    // test correct indent after newline on comment
24972    cx.set_state(indoc! {"
24973        # COMMENT:ˇ
24974    "});
24975    cx.update_editor(|editor, window, cx| {
24976        editor.newline(&Newline, window, cx);
24977    });
24978    cx.assert_editor_state(indoc! {"
24979        # COMMENT:
24980        ˇ
24981    "});
24982
24983    // test correct indent after newline after `then`
24984    cx.set_state(indoc! {"
24985
24986        if [ \"$1\" = \"test\" ]; thenˇ
24987    "});
24988    cx.update_editor(|editor, window, cx| {
24989        editor.newline(&Newline, window, cx);
24990    });
24991    cx.run_until_parked();
24992    cx.assert_editor_state(indoc! {"
24993
24994        if [ \"$1\" = \"test\" ]; then
24995            ˇ
24996    "});
24997
24998    // test correct indent after newline after `else`
24999    cx.set_state(indoc! {"
25000        if [ \"$1\" = \"test\" ]; then
25001        elseˇ
25002    "});
25003    cx.update_editor(|editor, window, cx| {
25004        editor.newline(&Newline, window, cx);
25005    });
25006    cx.run_until_parked();
25007    cx.assert_editor_state(indoc! {"
25008        if [ \"$1\" = \"test\" ]; then
25009        else
25010            ˇ
25011    "});
25012
25013    // test correct indent after newline after `elif`
25014    cx.set_state(indoc! {"
25015        if [ \"$1\" = \"test\" ]; then
25016        elifˇ
25017    "});
25018    cx.update_editor(|editor, window, cx| {
25019        editor.newline(&Newline, window, cx);
25020    });
25021    cx.run_until_parked();
25022    cx.assert_editor_state(indoc! {"
25023        if [ \"$1\" = \"test\" ]; then
25024        elif
25025            ˇ
25026    "});
25027
25028    // test correct indent after newline after `do`
25029    cx.set_state(indoc! {"
25030        for file in *.txt; doˇ
25031    "});
25032    cx.update_editor(|editor, window, cx| {
25033        editor.newline(&Newline, window, cx);
25034    });
25035    cx.run_until_parked();
25036    cx.assert_editor_state(indoc! {"
25037        for file in *.txt; do
25038            ˇ
25039    "});
25040
25041    // test correct indent after newline after case pattern
25042    cx.set_state(indoc! {"
25043        case \"$1\" in
25044            start)ˇ
25045    "});
25046    cx.update_editor(|editor, window, cx| {
25047        editor.newline(&Newline, window, cx);
25048    });
25049    cx.run_until_parked();
25050    cx.assert_editor_state(indoc! {"
25051        case \"$1\" in
25052            start)
25053                ˇ
25054    "});
25055
25056    // test correct indent after newline after case pattern
25057    cx.set_state(indoc! {"
25058        case \"$1\" in
25059            start)
25060                ;;
25061            *)ˇ
25062    "});
25063    cx.update_editor(|editor, window, cx| {
25064        editor.newline(&Newline, window, cx);
25065    });
25066    cx.run_until_parked();
25067    cx.assert_editor_state(indoc! {"
25068        case \"$1\" in
25069            start)
25070                ;;
25071            *)
25072                ˇ
25073    "});
25074
25075    // test correct indent after newline after function opening brace
25076    cx.set_state(indoc! {"
25077        function test() {ˇ}
25078    "});
25079    cx.update_editor(|editor, window, cx| {
25080        editor.newline(&Newline, window, cx);
25081    });
25082    cx.run_until_parked();
25083    cx.assert_editor_state(indoc! {"
25084        function test() {
25085            ˇ
25086        }
25087    "});
25088
25089    // test no extra indent after semicolon on same line
25090    cx.set_state(indoc! {"
25091        echo \"test\"25092    "});
25093    cx.update_editor(|editor, window, cx| {
25094        editor.newline(&Newline, window, cx);
25095    });
25096    cx.run_until_parked();
25097    cx.assert_editor_state(indoc! {"
25098        echo \"test\";
25099        ˇ
25100    "});
25101}
25102
25103fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
25104    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
25105    point..point
25106}
25107
25108#[track_caller]
25109fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
25110    let (text, ranges) = marked_text_ranges(marked_text, true);
25111    assert_eq!(editor.text(cx), text);
25112    assert_eq!(
25113        editor.selections.ranges(cx),
25114        ranges,
25115        "Assert selections are {}",
25116        marked_text
25117    );
25118}
25119
25120pub fn handle_signature_help_request(
25121    cx: &mut EditorLspTestContext,
25122    mocked_response: lsp::SignatureHelp,
25123) -> impl Future<Output = ()> + use<> {
25124    let mut request =
25125        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
25126            let mocked_response = mocked_response.clone();
25127            async move { Ok(Some(mocked_response)) }
25128        });
25129
25130    async move {
25131        request.next().await;
25132    }
25133}
25134
25135#[track_caller]
25136pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
25137    cx.update_editor(|editor, _, _| {
25138        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
25139            let entries = menu.entries.borrow();
25140            let entries = entries
25141                .iter()
25142                .map(|entry| entry.string.as_str())
25143                .collect::<Vec<_>>();
25144            assert_eq!(entries, expected);
25145        } else {
25146            panic!("Expected completions menu");
25147        }
25148    });
25149}
25150
25151/// Handle completion request passing a marked string specifying where the completion
25152/// should be triggered from using '|' character, what range should be replaced, and what completions
25153/// should be returned using '<' and '>' to delimit the range.
25154///
25155/// Also see `handle_completion_request_with_insert_and_replace`.
25156#[track_caller]
25157pub fn handle_completion_request(
25158    marked_string: &str,
25159    completions: Vec<&'static str>,
25160    is_incomplete: bool,
25161    counter: Arc<AtomicUsize>,
25162    cx: &mut EditorLspTestContext,
25163) -> impl Future<Output = ()> {
25164    let complete_from_marker: TextRangeMarker = '|'.into();
25165    let replace_range_marker: TextRangeMarker = ('<', '>').into();
25166    let (_, mut marked_ranges) = marked_text_ranges_by(
25167        marked_string,
25168        vec![complete_from_marker.clone(), replace_range_marker.clone()],
25169    );
25170
25171    let complete_from_position =
25172        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
25173    let replace_range =
25174        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
25175
25176    let mut request =
25177        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
25178            let completions = completions.clone();
25179            counter.fetch_add(1, atomic::Ordering::Release);
25180            async move {
25181                assert_eq!(params.text_document_position.text_document.uri, url.clone());
25182                assert_eq!(
25183                    params.text_document_position.position,
25184                    complete_from_position
25185                );
25186                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
25187                    is_incomplete,
25188                    item_defaults: None,
25189                    items: completions
25190                        .iter()
25191                        .map(|completion_text| lsp::CompletionItem {
25192                            label: completion_text.to_string(),
25193                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
25194                                range: replace_range,
25195                                new_text: completion_text.to_string(),
25196                            })),
25197                            ..Default::default()
25198                        })
25199                        .collect(),
25200                })))
25201            }
25202        });
25203
25204    async move {
25205        request.next().await;
25206    }
25207}
25208
25209/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
25210/// given instead, which also contains an `insert` range.
25211///
25212/// This function uses markers to define ranges:
25213/// - `|` marks the cursor position
25214/// - `<>` marks the replace range
25215/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
25216pub fn handle_completion_request_with_insert_and_replace(
25217    cx: &mut EditorLspTestContext,
25218    marked_string: &str,
25219    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
25220    counter: Arc<AtomicUsize>,
25221) -> impl Future<Output = ()> {
25222    let complete_from_marker: TextRangeMarker = '|'.into();
25223    let replace_range_marker: TextRangeMarker = ('<', '>').into();
25224    let insert_range_marker: TextRangeMarker = ('{', '}').into();
25225
25226    let (_, mut marked_ranges) = marked_text_ranges_by(
25227        marked_string,
25228        vec![
25229            complete_from_marker.clone(),
25230            replace_range_marker.clone(),
25231            insert_range_marker.clone(),
25232        ],
25233    );
25234
25235    let complete_from_position =
25236        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
25237    let replace_range =
25238        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
25239
25240    let insert_range = match marked_ranges.remove(&insert_range_marker) {
25241        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
25242        _ => lsp::Range {
25243            start: replace_range.start,
25244            end: complete_from_position,
25245        },
25246    };
25247
25248    let mut request =
25249        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
25250            let completions = completions.clone();
25251            counter.fetch_add(1, atomic::Ordering::Release);
25252            async move {
25253                assert_eq!(params.text_document_position.text_document.uri, url.clone());
25254                assert_eq!(
25255                    params.text_document_position.position, complete_from_position,
25256                    "marker `|` position doesn't match",
25257                );
25258                Ok(Some(lsp::CompletionResponse::Array(
25259                    completions
25260                        .iter()
25261                        .map(|(label, new_text)| lsp::CompletionItem {
25262                            label: label.to_string(),
25263                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
25264                                lsp::InsertReplaceEdit {
25265                                    insert: insert_range,
25266                                    replace: replace_range,
25267                                    new_text: new_text.to_string(),
25268                                },
25269                            )),
25270                            ..Default::default()
25271                        })
25272                        .collect(),
25273                )))
25274            }
25275        });
25276
25277    async move {
25278        request.next().await;
25279    }
25280}
25281
25282fn handle_resolve_completion_request(
25283    cx: &mut EditorLspTestContext,
25284    edits: Option<Vec<(&'static str, &'static str)>>,
25285) -> impl Future<Output = ()> {
25286    let edits = edits.map(|edits| {
25287        edits
25288            .iter()
25289            .map(|(marked_string, new_text)| {
25290                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
25291                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
25292                lsp::TextEdit::new(replace_range, new_text.to_string())
25293            })
25294            .collect::<Vec<_>>()
25295    });
25296
25297    let mut request =
25298        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
25299            let edits = edits.clone();
25300            async move {
25301                Ok(lsp::CompletionItem {
25302                    additional_text_edits: edits,
25303                    ..Default::default()
25304                })
25305            }
25306        });
25307
25308    async move {
25309        request.next().await;
25310    }
25311}
25312
25313pub(crate) fn update_test_language_settings(
25314    cx: &mut TestAppContext,
25315    f: impl Fn(&mut AllLanguageSettingsContent),
25316) {
25317    cx.update(|cx| {
25318        SettingsStore::update_global(cx, |store, cx| {
25319            store.update_user_settings(cx, |settings| f(&mut settings.project.all_languages));
25320        });
25321    });
25322}
25323
25324pub(crate) fn update_test_project_settings(
25325    cx: &mut TestAppContext,
25326    f: impl Fn(&mut ProjectSettingsContent),
25327) {
25328    cx.update(|cx| {
25329        SettingsStore::update_global(cx, |store, cx| {
25330            store.update_user_settings(cx, |settings| f(&mut settings.project));
25331        });
25332    });
25333}
25334
25335pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
25336    cx.update(|cx| {
25337        assets::Assets.load_test_fonts(cx);
25338        let store = SettingsStore::test(cx);
25339        cx.set_global(store);
25340        theme::init(theme::LoadThemes::JustBase, cx);
25341        release_channel::init(SemanticVersion::default(), cx);
25342        client::init_settings(cx);
25343        language::init(cx);
25344        Project::init_settings(cx);
25345        workspace::init_settings(cx);
25346        crate::init(cx);
25347    });
25348    zlog::init_test();
25349    update_test_language_settings(cx, f);
25350}
25351
25352#[track_caller]
25353fn assert_hunk_revert(
25354    not_reverted_text_with_selections: &str,
25355    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
25356    expected_reverted_text_with_selections: &str,
25357    base_text: &str,
25358    cx: &mut EditorLspTestContext,
25359) {
25360    cx.set_state(not_reverted_text_with_selections);
25361    cx.set_head_text(base_text);
25362    cx.executor().run_until_parked();
25363
25364    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
25365        let snapshot = editor.snapshot(window, cx);
25366        let reverted_hunk_statuses = snapshot
25367            .buffer_snapshot()
25368            .diff_hunks_in_range(0..snapshot.buffer_snapshot().len())
25369            .map(|hunk| hunk.status().kind)
25370            .collect::<Vec<_>>();
25371
25372        editor.git_restore(&Default::default(), window, cx);
25373        reverted_hunk_statuses
25374    });
25375    cx.executor().run_until_parked();
25376    cx.assert_editor_state(expected_reverted_text_with_selections);
25377    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
25378}
25379
25380#[gpui::test(iterations = 10)]
25381async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
25382    init_test(cx, |_| {});
25383
25384    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
25385    let counter = diagnostic_requests.clone();
25386
25387    let fs = FakeFs::new(cx.executor());
25388    fs.insert_tree(
25389        path!("/a"),
25390        json!({
25391            "first.rs": "fn main() { let a = 5; }",
25392            "second.rs": "// Test file",
25393        }),
25394    )
25395    .await;
25396
25397    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25398    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25399    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25400
25401    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25402    language_registry.add(rust_lang());
25403    let mut fake_servers = language_registry.register_fake_lsp(
25404        "Rust",
25405        FakeLspAdapter {
25406            capabilities: lsp::ServerCapabilities {
25407                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
25408                    lsp::DiagnosticOptions {
25409                        identifier: None,
25410                        inter_file_dependencies: true,
25411                        workspace_diagnostics: true,
25412                        work_done_progress_options: Default::default(),
25413                    },
25414                )),
25415                ..Default::default()
25416            },
25417            ..Default::default()
25418        },
25419    );
25420
25421    let editor = workspace
25422        .update(cx, |workspace, window, cx| {
25423            workspace.open_abs_path(
25424                PathBuf::from(path!("/a/first.rs")),
25425                OpenOptions::default(),
25426                window,
25427                cx,
25428            )
25429        })
25430        .unwrap()
25431        .await
25432        .unwrap()
25433        .downcast::<Editor>()
25434        .unwrap();
25435    let fake_server = fake_servers.next().await.unwrap();
25436    let server_id = fake_server.server.server_id();
25437    let mut first_request = fake_server
25438        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
25439            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
25440            let result_id = Some(new_result_id.to_string());
25441            assert_eq!(
25442                params.text_document.uri,
25443                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25444            );
25445            async move {
25446                Ok(lsp::DocumentDiagnosticReportResult::Report(
25447                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
25448                        related_documents: None,
25449                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
25450                            items: Vec::new(),
25451                            result_id,
25452                        },
25453                    }),
25454                ))
25455            }
25456        });
25457
25458    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
25459        project.update(cx, |project, cx| {
25460            let buffer_id = editor
25461                .read(cx)
25462                .buffer()
25463                .read(cx)
25464                .as_singleton()
25465                .expect("created a singleton buffer")
25466                .read(cx)
25467                .remote_id();
25468            let buffer_result_id = project
25469                .lsp_store()
25470                .read(cx)
25471                .result_id(server_id, buffer_id, cx);
25472            assert_eq!(expected, buffer_result_id);
25473        });
25474    };
25475
25476    ensure_result_id(None, cx);
25477    cx.executor().advance_clock(Duration::from_millis(60));
25478    cx.executor().run_until_parked();
25479    assert_eq!(
25480        diagnostic_requests.load(atomic::Ordering::Acquire),
25481        1,
25482        "Opening file should trigger diagnostic request"
25483    );
25484    first_request
25485        .next()
25486        .await
25487        .expect("should have sent the first diagnostics pull request");
25488    ensure_result_id(Some("1".to_string()), cx);
25489
25490    // Editing should trigger diagnostics
25491    editor.update_in(cx, |editor, window, cx| {
25492        editor.handle_input("2", window, cx)
25493    });
25494    cx.executor().advance_clock(Duration::from_millis(60));
25495    cx.executor().run_until_parked();
25496    assert_eq!(
25497        diagnostic_requests.load(atomic::Ordering::Acquire),
25498        2,
25499        "Editing should trigger diagnostic request"
25500    );
25501    ensure_result_id(Some("2".to_string()), cx);
25502
25503    // Moving cursor should not trigger diagnostic request
25504    editor.update_in(cx, |editor, window, cx| {
25505        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25506            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
25507        });
25508    });
25509    cx.executor().advance_clock(Duration::from_millis(60));
25510    cx.executor().run_until_parked();
25511    assert_eq!(
25512        diagnostic_requests.load(atomic::Ordering::Acquire),
25513        2,
25514        "Cursor movement should not trigger diagnostic request"
25515    );
25516    ensure_result_id(Some("2".to_string()), cx);
25517    // Multiple rapid edits should be debounced
25518    for _ in 0..5 {
25519        editor.update_in(cx, |editor, window, cx| {
25520            editor.handle_input("x", window, cx)
25521        });
25522    }
25523    cx.executor().advance_clock(Duration::from_millis(60));
25524    cx.executor().run_until_parked();
25525
25526    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
25527    assert!(
25528        final_requests <= 4,
25529        "Multiple rapid edits should be debounced (got {final_requests} requests)",
25530    );
25531    ensure_result_id(Some(final_requests.to_string()), cx);
25532}
25533
25534#[gpui::test]
25535async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
25536    // Regression test for issue #11671
25537    // Previously, adding a cursor after moving multiple cursors would reset
25538    // the cursor count instead of adding to the existing cursors.
25539    init_test(cx, |_| {});
25540    let mut cx = EditorTestContext::new(cx).await;
25541
25542    // Create a simple buffer with cursor at start
25543    cx.set_state(indoc! {"
25544        ˇaaaa
25545        bbbb
25546        cccc
25547        dddd
25548        eeee
25549        ffff
25550        gggg
25551        hhhh"});
25552
25553    // Add 2 cursors below (so we have 3 total)
25554    cx.update_editor(|editor, window, cx| {
25555        editor.add_selection_below(&Default::default(), window, cx);
25556        editor.add_selection_below(&Default::default(), window, cx);
25557    });
25558
25559    // Verify we have 3 cursors
25560    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
25561    assert_eq!(
25562        initial_count, 3,
25563        "Should have 3 cursors after adding 2 below"
25564    );
25565
25566    // Move down one line
25567    cx.update_editor(|editor, window, cx| {
25568        editor.move_down(&MoveDown, window, cx);
25569    });
25570
25571    // Add another cursor below
25572    cx.update_editor(|editor, window, cx| {
25573        editor.add_selection_below(&Default::default(), window, cx);
25574    });
25575
25576    // Should now have 4 cursors (3 original + 1 new)
25577    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
25578    assert_eq!(
25579        final_count, 4,
25580        "Should have 4 cursors after moving and adding another"
25581    );
25582}
25583
25584#[gpui::test(iterations = 10)]
25585async fn test_document_colors(cx: &mut TestAppContext) {
25586    let expected_color = Rgba {
25587        r: 0.33,
25588        g: 0.33,
25589        b: 0.33,
25590        a: 0.33,
25591    };
25592
25593    init_test(cx, |_| {});
25594
25595    let fs = FakeFs::new(cx.executor());
25596    fs.insert_tree(
25597        path!("/a"),
25598        json!({
25599            "first.rs": "fn main() { let a = 5; }",
25600        }),
25601    )
25602    .await;
25603
25604    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25605    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25606    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25607
25608    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25609    language_registry.add(rust_lang());
25610    let mut fake_servers = language_registry.register_fake_lsp(
25611        "Rust",
25612        FakeLspAdapter {
25613            capabilities: lsp::ServerCapabilities {
25614                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
25615                ..lsp::ServerCapabilities::default()
25616            },
25617            name: "rust-analyzer",
25618            ..FakeLspAdapter::default()
25619        },
25620    );
25621    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
25622        "Rust",
25623        FakeLspAdapter {
25624            capabilities: lsp::ServerCapabilities {
25625                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
25626                ..lsp::ServerCapabilities::default()
25627            },
25628            name: "not-rust-analyzer",
25629            ..FakeLspAdapter::default()
25630        },
25631    );
25632
25633    let editor = workspace
25634        .update(cx, |workspace, window, cx| {
25635            workspace.open_abs_path(
25636                PathBuf::from(path!("/a/first.rs")),
25637                OpenOptions::default(),
25638                window,
25639                cx,
25640            )
25641        })
25642        .unwrap()
25643        .await
25644        .unwrap()
25645        .downcast::<Editor>()
25646        .unwrap();
25647    let fake_language_server = fake_servers.next().await.unwrap();
25648    let fake_language_server_without_capabilities =
25649        fake_servers_without_capabilities.next().await.unwrap();
25650    let requests_made = Arc::new(AtomicUsize::new(0));
25651    let closure_requests_made = Arc::clone(&requests_made);
25652    let mut color_request_handle = fake_language_server
25653        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
25654            let requests_made = Arc::clone(&closure_requests_made);
25655            async move {
25656                assert_eq!(
25657                    params.text_document.uri,
25658                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25659                );
25660                requests_made.fetch_add(1, atomic::Ordering::Release);
25661                Ok(vec![
25662                    lsp::ColorInformation {
25663                        range: lsp::Range {
25664                            start: lsp::Position {
25665                                line: 0,
25666                                character: 0,
25667                            },
25668                            end: lsp::Position {
25669                                line: 0,
25670                                character: 1,
25671                            },
25672                        },
25673                        color: lsp::Color {
25674                            red: 0.33,
25675                            green: 0.33,
25676                            blue: 0.33,
25677                            alpha: 0.33,
25678                        },
25679                    },
25680                    lsp::ColorInformation {
25681                        range: lsp::Range {
25682                            start: lsp::Position {
25683                                line: 0,
25684                                character: 0,
25685                            },
25686                            end: lsp::Position {
25687                                line: 0,
25688                                character: 1,
25689                            },
25690                        },
25691                        color: lsp::Color {
25692                            red: 0.33,
25693                            green: 0.33,
25694                            blue: 0.33,
25695                            alpha: 0.33,
25696                        },
25697                    },
25698                ])
25699            }
25700        });
25701
25702    let _handle = fake_language_server_without_capabilities
25703        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
25704            panic!("Should not be called");
25705        });
25706    cx.executor().advance_clock(Duration::from_millis(100));
25707    color_request_handle.next().await.unwrap();
25708    cx.run_until_parked();
25709    assert_eq!(
25710        1,
25711        requests_made.load(atomic::Ordering::Acquire),
25712        "Should query for colors once per editor open"
25713    );
25714    editor.update_in(cx, |editor, _, cx| {
25715        assert_eq!(
25716            vec![expected_color],
25717            extract_color_inlays(editor, cx),
25718            "Should have an initial inlay"
25719        );
25720    });
25721
25722    // opening another file in a split should not influence the LSP query counter
25723    workspace
25724        .update(cx, |workspace, window, cx| {
25725            assert_eq!(
25726                workspace.panes().len(),
25727                1,
25728                "Should have one pane with one editor"
25729            );
25730            workspace.move_item_to_pane_in_direction(
25731                &MoveItemToPaneInDirection {
25732                    direction: SplitDirection::Right,
25733                    focus: false,
25734                    clone: true,
25735                },
25736                window,
25737                cx,
25738            );
25739        })
25740        .unwrap();
25741    cx.run_until_parked();
25742    workspace
25743        .update(cx, |workspace, _, cx| {
25744            let panes = workspace.panes();
25745            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
25746            for pane in panes {
25747                let editor = pane
25748                    .read(cx)
25749                    .active_item()
25750                    .and_then(|item| item.downcast::<Editor>())
25751                    .expect("Should have opened an editor in each split");
25752                let editor_file = editor
25753                    .read(cx)
25754                    .buffer()
25755                    .read(cx)
25756                    .as_singleton()
25757                    .expect("test deals with singleton buffers")
25758                    .read(cx)
25759                    .file()
25760                    .expect("test buffese should have a file")
25761                    .path();
25762                assert_eq!(
25763                    editor_file.as_ref(),
25764                    rel_path("first.rs"),
25765                    "Both editors should be opened for the same file"
25766                )
25767            }
25768        })
25769        .unwrap();
25770
25771    cx.executor().advance_clock(Duration::from_millis(500));
25772    let save = editor.update_in(cx, |editor, window, cx| {
25773        editor.move_to_end(&MoveToEnd, window, cx);
25774        editor.handle_input("dirty", window, cx);
25775        editor.save(
25776            SaveOptions {
25777                format: true,
25778                autosave: true,
25779            },
25780            project.clone(),
25781            window,
25782            cx,
25783        )
25784    });
25785    save.await.unwrap();
25786
25787    color_request_handle.next().await.unwrap();
25788    cx.run_until_parked();
25789    assert_eq!(
25790        3,
25791        requests_made.load(atomic::Ordering::Acquire),
25792        "Should query for colors once per save and once per formatting after save"
25793    );
25794
25795    drop(editor);
25796    let close = workspace
25797        .update(cx, |workspace, window, cx| {
25798            workspace.active_pane().update(cx, |pane, cx| {
25799                pane.close_active_item(&CloseActiveItem::default(), window, cx)
25800            })
25801        })
25802        .unwrap();
25803    close.await.unwrap();
25804    let close = workspace
25805        .update(cx, |workspace, window, cx| {
25806            workspace.active_pane().update(cx, |pane, cx| {
25807                pane.close_active_item(&CloseActiveItem::default(), window, cx)
25808            })
25809        })
25810        .unwrap();
25811    close.await.unwrap();
25812    assert_eq!(
25813        3,
25814        requests_made.load(atomic::Ordering::Acquire),
25815        "After saving and closing all editors, no extra requests should be made"
25816    );
25817    workspace
25818        .update(cx, |workspace, _, cx| {
25819            assert!(
25820                workspace.active_item(cx).is_none(),
25821                "Should close all editors"
25822            )
25823        })
25824        .unwrap();
25825
25826    workspace
25827        .update(cx, |workspace, window, cx| {
25828            workspace.active_pane().update(cx, |pane, cx| {
25829                pane.navigate_backward(&workspace::GoBack, window, cx);
25830            })
25831        })
25832        .unwrap();
25833    cx.executor().advance_clock(Duration::from_millis(100));
25834    cx.run_until_parked();
25835    let editor = workspace
25836        .update(cx, |workspace, _, cx| {
25837            workspace
25838                .active_item(cx)
25839                .expect("Should have reopened the editor again after navigating back")
25840                .downcast::<Editor>()
25841                .expect("Should be an editor")
25842        })
25843        .unwrap();
25844    color_request_handle.next().await.unwrap();
25845    assert_eq!(
25846        3,
25847        requests_made.load(atomic::Ordering::Acquire),
25848        "Cache should be reused on buffer close and reopen"
25849    );
25850    editor.update(cx, |editor, cx| {
25851        assert_eq!(
25852            vec![expected_color],
25853            extract_color_inlays(editor, cx),
25854            "Should have an initial inlay"
25855        );
25856    });
25857
25858    drop(color_request_handle);
25859    let closure_requests_made = Arc::clone(&requests_made);
25860    let mut empty_color_request_handle = fake_language_server
25861        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
25862            let requests_made = Arc::clone(&closure_requests_made);
25863            async move {
25864                assert_eq!(
25865                    params.text_document.uri,
25866                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25867                );
25868                requests_made.fetch_add(1, atomic::Ordering::Release);
25869                Ok(Vec::new())
25870            }
25871        });
25872    let save = editor.update_in(cx, |editor, window, cx| {
25873        editor.move_to_end(&MoveToEnd, window, cx);
25874        editor.handle_input("dirty_again", window, cx);
25875        editor.save(
25876            SaveOptions {
25877                format: false,
25878                autosave: true,
25879            },
25880            project.clone(),
25881            window,
25882            cx,
25883        )
25884    });
25885    save.await.unwrap();
25886
25887    empty_color_request_handle.next().await.unwrap();
25888    cx.run_until_parked();
25889    assert_eq!(
25890        4,
25891        requests_made.load(atomic::Ordering::Acquire),
25892        "Should query for colors once per save only, as formatting was not requested"
25893    );
25894    editor.update(cx, |editor, cx| {
25895        assert_eq!(
25896            Vec::<Rgba>::new(),
25897            extract_color_inlays(editor, cx),
25898            "Should clear all colors when the server returns an empty response"
25899        );
25900    });
25901}
25902
25903#[gpui::test]
25904async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
25905    init_test(cx, |_| {});
25906    let (editor, cx) = cx.add_window_view(Editor::single_line);
25907    editor.update_in(cx, |editor, window, cx| {
25908        editor.set_text("oops\n\nwow\n", window, cx)
25909    });
25910    cx.run_until_parked();
25911    editor.update(cx, |editor, cx| {
25912        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
25913    });
25914    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
25915    cx.run_until_parked();
25916    editor.update(cx, |editor, cx| {
25917        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
25918    });
25919}
25920
25921#[gpui::test]
25922async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
25923    init_test(cx, |_| {});
25924
25925    cx.update(|cx| {
25926        register_project_item::<Editor>(cx);
25927    });
25928
25929    let fs = FakeFs::new(cx.executor());
25930    fs.insert_tree("/root1", json!({})).await;
25931    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
25932        .await;
25933
25934    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
25935    let (workspace, cx) =
25936        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
25937
25938    let worktree_id = project.update(cx, |project, cx| {
25939        project.worktrees(cx).next().unwrap().read(cx).id()
25940    });
25941
25942    let handle = workspace
25943        .update_in(cx, |workspace, window, cx| {
25944            let project_path = (worktree_id, rel_path("one.pdf"));
25945            workspace.open_path(project_path, None, true, window, cx)
25946        })
25947        .await
25948        .unwrap();
25949
25950    assert_eq!(
25951        handle.to_any().entity_type(),
25952        TypeId::of::<InvalidBufferView>()
25953    );
25954}
25955
25956#[gpui::test]
25957async fn test_select_next_prev_syntax_node(cx: &mut TestAppContext) {
25958    init_test(cx, |_| {});
25959
25960    let language = Arc::new(Language::new(
25961        LanguageConfig::default(),
25962        Some(tree_sitter_rust::LANGUAGE.into()),
25963    ));
25964
25965    // Test hierarchical sibling navigation
25966    let text = r#"
25967        fn outer() {
25968            if condition {
25969                let a = 1;
25970            }
25971            let b = 2;
25972        }
25973
25974        fn another() {
25975            let c = 3;
25976        }
25977    "#;
25978
25979    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
25980    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
25981    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
25982
25983    // Wait for parsing to complete
25984    editor
25985        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
25986        .await;
25987
25988    editor.update_in(cx, |editor, window, cx| {
25989        // Start by selecting "let a = 1;" inside the if block
25990        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25991            s.select_display_ranges([
25992                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 26)
25993            ]);
25994        });
25995
25996        let initial_selection = editor.selections.display_ranges(cx);
25997        assert_eq!(initial_selection.len(), 1, "Should have one selection");
25998
25999        // Test select next sibling - should move up levels to find the next sibling
26000        // Since "let a = 1;" has no siblings in the if block, it should move up
26001        // to find "let b = 2;" which is a sibling of the if block
26002        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
26003        let next_selection = editor.selections.display_ranges(cx);
26004
26005        // Should have a selection and it should be different from the initial
26006        assert_eq!(
26007            next_selection.len(),
26008            1,
26009            "Should have one selection after next"
26010        );
26011        assert_ne!(
26012            next_selection[0], initial_selection[0],
26013            "Next sibling selection should be different"
26014        );
26015
26016        // Test hierarchical navigation by going to the end of the current function
26017        // and trying to navigate to the next function
26018        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26019            s.select_display_ranges([
26020                DisplayPoint::new(DisplayRow(5), 12)..DisplayPoint::new(DisplayRow(5), 22)
26021            ]);
26022        });
26023
26024        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
26025        let function_next_selection = editor.selections.display_ranges(cx);
26026
26027        // Should move to the next function
26028        assert_eq!(
26029            function_next_selection.len(),
26030            1,
26031            "Should have one selection after function next"
26032        );
26033
26034        // Test select previous sibling navigation
26035        editor.select_prev_syntax_node(&SelectPreviousSyntaxNode, window, cx);
26036        let prev_selection = editor.selections.display_ranges(cx);
26037
26038        // Should have a selection and it should be different
26039        assert_eq!(
26040            prev_selection.len(),
26041            1,
26042            "Should have one selection after prev"
26043        );
26044        assert_ne!(
26045            prev_selection[0], function_next_selection[0],
26046            "Previous sibling selection should be different from next"
26047        );
26048    });
26049}
26050
26051#[gpui::test]
26052async fn test_next_prev_document_highlight(cx: &mut TestAppContext) {
26053    init_test(cx, |_| {});
26054
26055    let mut cx = EditorTestContext::new(cx).await;
26056    cx.set_state(
26057        "let ˇvariable = 42;
26058let another = variable + 1;
26059let result = variable * 2;",
26060    );
26061
26062    // Set up document highlights manually (simulating LSP response)
26063    cx.update_editor(|editor, _window, cx| {
26064        let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
26065
26066        // Create highlights for "variable" occurrences
26067        let highlight_ranges = [
26068            Point::new(0, 4)..Point::new(0, 12),  // First "variable"
26069            Point::new(1, 14)..Point::new(1, 22), // Second "variable"
26070            Point::new(2, 13)..Point::new(2, 21), // Third "variable"
26071        ];
26072
26073        let anchor_ranges: Vec<_> = highlight_ranges
26074            .iter()
26075            .map(|range| range.clone().to_anchors(&buffer_snapshot))
26076            .collect();
26077
26078        editor.highlight_background::<DocumentHighlightRead>(
26079            &anchor_ranges,
26080            |theme| theme.colors().editor_document_highlight_read_background,
26081            cx,
26082        );
26083    });
26084
26085    // Go to next highlight - should move to second "variable"
26086    cx.update_editor(|editor, window, cx| {
26087        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26088    });
26089    cx.assert_editor_state(
26090        "let variable = 42;
26091let another = ˇvariable + 1;
26092let result = variable * 2;",
26093    );
26094
26095    // Go to next highlight - should move to third "variable"
26096    cx.update_editor(|editor, window, cx| {
26097        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26098    });
26099    cx.assert_editor_state(
26100        "let variable = 42;
26101let another = variable + 1;
26102let result = ˇvariable * 2;",
26103    );
26104
26105    // Go to next highlight - should stay at third "variable" (no wrap-around)
26106    cx.update_editor(|editor, window, cx| {
26107        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26108    });
26109    cx.assert_editor_state(
26110        "let variable = 42;
26111let another = variable + 1;
26112let result = ˇvariable * 2;",
26113    );
26114
26115    // Now test going backwards from third position
26116    cx.update_editor(|editor, window, cx| {
26117        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26118    });
26119    cx.assert_editor_state(
26120        "let variable = 42;
26121let another = ˇvariable + 1;
26122let result = variable * 2;",
26123    );
26124
26125    // Go to previous highlight - should move to first "variable"
26126    cx.update_editor(|editor, window, cx| {
26127        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26128    });
26129    cx.assert_editor_state(
26130        "let ˇvariable = 42;
26131let another = variable + 1;
26132let result = variable * 2;",
26133    );
26134
26135    // Go to previous highlight - should stay on first "variable"
26136    cx.update_editor(|editor, window, cx| {
26137        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26138    });
26139    cx.assert_editor_state(
26140        "let ˇvariable = 42;
26141let another = variable + 1;
26142let result = variable * 2;",
26143    );
26144}
26145
26146#[gpui::test]
26147async fn test_paste_url_from_other_app_creates_markdown_link_over_selected_text(
26148    cx: &mut gpui::TestAppContext,
26149) {
26150    init_test(cx, |_| {});
26151
26152    let url = "https://zed.dev";
26153
26154    let markdown_language = Arc::new(Language::new(
26155        LanguageConfig {
26156            name: "Markdown".into(),
26157            ..LanguageConfig::default()
26158        },
26159        None,
26160    ));
26161
26162    let mut cx = EditorTestContext::new(cx).await;
26163    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26164    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)");
26165
26166    cx.update_editor(|editor, window, cx| {
26167        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26168        editor.paste(&Paste, window, cx);
26169    });
26170
26171    cx.assert_editor_state(&format!(
26172        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)"
26173    ));
26174}
26175
26176#[gpui::test]
26177async fn test_paste_url_from_zed_copy_creates_markdown_link_over_selected_text(
26178    cx: &mut gpui::TestAppContext,
26179) {
26180    init_test(cx, |_| {});
26181
26182    let url = "https://zed.dev";
26183
26184    let markdown_language = Arc::new(Language::new(
26185        LanguageConfig {
26186            name: "Markdown".into(),
26187            ..LanguageConfig::default()
26188        },
26189        None,
26190    ));
26191
26192    let mut cx = EditorTestContext::new(cx).await;
26193    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26194    cx.set_state(&format!(
26195        "Hello, editor.\nZed is great (see this link: )\n«{url}ˇ»"
26196    ));
26197
26198    cx.update_editor(|editor, window, cx| {
26199        editor.copy(&Copy, window, cx);
26200    });
26201
26202    cx.set_state(&format!(
26203        "Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)\n{url}"
26204    ));
26205
26206    cx.update_editor(|editor, window, cx| {
26207        editor.paste(&Paste, window, cx);
26208    });
26209
26210    cx.assert_editor_state(&format!(
26211        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)\n{url}"
26212    ));
26213}
26214
26215#[gpui::test]
26216async fn test_paste_url_from_other_app_replaces_existing_url_without_creating_markdown_link(
26217    cx: &mut gpui::TestAppContext,
26218) {
26219    init_test(cx, |_| {});
26220
26221    let url = "https://zed.dev";
26222
26223    let markdown_language = Arc::new(Language::new(
26224        LanguageConfig {
26225            name: "Markdown".into(),
26226            ..LanguageConfig::default()
26227        },
26228        None,
26229    ));
26230
26231    let mut cx = EditorTestContext::new(cx).await;
26232    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26233    cx.set_state("Please visit zed's homepage: «https://www.apple.comˇ»");
26234
26235    cx.update_editor(|editor, window, cx| {
26236        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26237        editor.paste(&Paste, window, cx);
26238    });
26239
26240    cx.assert_editor_state(&format!("Please visit zed's homepage: {url}ˇ"));
26241}
26242
26243#[gpui::test]
26244async fn test_paste_plain_text_from_other_app_replaces_selection_without_creating_markdown_link(
26245    cx: &mut gpui::TestAppContext,
26246) {
26247    init_test(cx, |_| {});
26248
26249    let text = "Awesome";
26250
26251    let markdown_language = Arc::new(Language::new(
26252        LanguageConfig {
26253            name: "Markdown".into(),
26254            ..LanguageConfig::default()
26255        },
26256        None,
26257    ));
26258
26259    let mut cx = EditorTestContext::new(cx).await;
26260    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26261    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat»");
26262
26263    cx.update_editor(|editor, window, cx| {
26264        cx.write_to_clipboard(ClipboardItem::new_string(text.to_string()));
26265        editor.paste(&Paste, window, cx);
26266    });
26267
26268    cx.assert_editor_state(&format!("Hello, {text}ˇ.\nZed is {text}ˇ"));
26269}
26270
26271#[gpui::test]
26272async fn test_paste_url_from_other_app_without_creating_markdown_link_in_non_markdown_language(
26273    cx: &mut gpui::TestAppContext,
26274) {
26275    init_test(cx, |_| {});
26276
26277    let url = "https://zed.dev";
26278
26279    let markdown_language = Arc::new(Language::new(
26280        LanguageConfig {
26281            name: "Rust".into(),
26282            ..LanguageConfig::default()
26283        },
26284        None,
26285    ));
26286
26287    let mut cx = EditorTestContext::new(cx).await;
26288    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26289    cx.set_state("// Hello, «editorˇ».\n// Zed is «ˇgreat» (see this link: ˇ)");
26290
26291    cx.update_editor(|editor, window, cx| {
26292        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26293        editor.paste(&Paste, window, cx);
26294    });
26295
26296    cx.assert_editor_state(&format!(
26297        "// Hello, {url}ˇ.\n// Zed is {url}ˇ (see this link: {url}ˇ)"
26298    ));
26299}
26300
26301#[gpui::test]
26302async fn test_paste_url_from_other_app_creates_markdown_link_selectively_in_multi_buffer(
26303    cx: &mut TestAppContext,
26304) {
26305    init_test(cx, |_| {});
26306
26307    let url = "https://zed.dev";
26308
26309    let markdown_language = Arc::new(Language::new(
26310        LanguageConfig {
26311            name: "Markdown".into(),
26312            ..LanguageConfig::default()
26313        },
26314        None,
26315    ));
26316
26317    let (editor, cx) = cx.add_window_view(|window, cx| {
26318        let multi_buffer = MultiBuffer::build_multi(
26319            [
26320                ("this will embed -> link", vec![Point::row_range(0..1)]),
26321                ("this will replace -> link", vec![Point::row_range(0..1)]),
26322            ],
26323            cx,
26324        );
26325        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
26326        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26327            s.select_ranges(vec![
26328                Point::new(0, 19)..Point::new(0, 23),
26329                Point::new(1, 21)..Point::new(1, 25),
26330            ])
26331        });
26332        let first_buffer_id = multi_buffer
26333            .read(cx)
26334            .excerpt_buffer_ids()
26335            .into_iter()
26336            .next()
26337            .unwrap();
26338        let first_buffer = multi_buffer.read(cx).buffer(first_buffer_id).unwrap();
26339        first_buffer.update(cx, |buffer, cx| {
26340            buffer.set_language(Some(markdown_language.clone()), cx);
26341        });
26342
26343        editor
26344    });
26345    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
26346
26347    cx.update_editor(|editor, window, cx| {
26348        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26349        editor.paste(&Paste, window, cx);
26350    });
26351
26352    cx.assert_editor_state(&format!(
26353        "this will embed -> [link]({url}\nthis will replace -> {url}ˇ"
26354    ));
26355}
26356
26357#[gpui::test]
26358async fn test_race_in_multibuffer_save(cx: &mut TestAppContext) {
26359    init_test(cx, |_| {});
26360
26361    let fs = FakeFs::new(cx.executor());
26362    fs.insert_tree(
26363        path!("/project"),
26364        json!({
26365            "first.rs": "# First Document\nSome content here.",
26366            "second.rs": "Plain text content for second file.",
26367        }),
26368    )
26369    .await;
26370
26371    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
26372    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
26373    let cx = &mut VisualTestContext::from_window(*workspace, cx);
26374
26375    let language = rust_lang();
26376    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
26377    language_registry.add(language.clone());
26378    let mut fake_servers = language_registry.register_fake_lsp(
26379        "Rust",
26380        FakeLspAdapter {
26381            ..FakeLspAdapter::default()
26382        },
26383    );
26384
26385    let buffer1 = project
26386        .update(cx, |project, cx| {
26387            project.open_local_buffer(PathBuf::from(path!("/project/first.rs")), cx)
26388        })
26389        .await
26390        .unwrap();
26391    let buffer2 = project
26392        .update(cx, |project, cx| {
26393            project.open_local_buffer(PathBuf::from(path!("/project/second.rs")), cx)
26394        })
26395        .await
26396        .unwrap();
26397
26398    let multi_buffer = cx.new(|cx| {
26399        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
26400        multi_buffer.set_excerpts_for_path(
26401            PathKey::for_buffer(&buffer1, cx),
26402            buffer1.clone(),
26403            [Point::zero()..buffer1.read(cx).max_point()],
26404            3,
26405            cx,
26406        );
26407        multi_buffer.set_excerpts_for_path(
26408            PathKey::for_buffer(&buffer2, cx),
26409            buffer2.clone(),
26410            [Point::zero()..buffer1.read(cx).max_point()],
26411            3,
26412            cx,
26413        );
26414        multi_buffer
26415    });
26416
26417    let (editor, cx) = cx.add_window_view(|window, cx| {
26418        Editor::new(
26419            EditorMode::full(),
26420            multi_buffer,
26421            Some(project.clone()),
26422            window,
26423            cx,
26424        )
26425    });
26426
26427    let fake_language_server = fake_servers.next().await.unwrap();
26428
26429    buffer1.update(cx, |buffer, cx| buffer.edit([(0..0, "hello!")], None, cx));
26430
26431    let save = editor.update_in(cx, |editor, window, cx| {
26432        assert!(editor.is_dirty(cx));
26433
26434        editor.save(
26435            SaveOptions {
26436                format: true,
26437                autosave: true,
26438            },
26439            project,
26440            window,
26441            cx,
26442        )
26443    });
26444    let (start_edit_tx, start_edit_rx) = oneshot::channel();
26445    let (done_edit_tx, done_edit_rx) = oneshot::channel();
26446    let mut done_edit_rx = Some(done_edit_rx);
26447    let mut start_edit_tx = Some(start_edit_tx);
26448
26449    fake_language_server.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| {
26450        start_edit_tx.take().unwrap().send(()).unwrap();
26451        let done_edit_rx = done_edit_rx.take().unwrap();
26452        async move {
26453            done_edit_rx.await.unwrap();
26454            Ok(None)
26455        }
26456    });
26457
26458    start_edit_rx.await.unwrap();
26459    buffer2
26460        .update(cx, |buffer, cx| buffer.edit([(0..0, "world!")], None, cx))
26461        .unwrap();
26462
26463    done_edit_tx.send(()).unwrap();
26464
26465    save.await.unwrap();
26466    cx.update(|_, cx| assert!(editor.is_dirty(cx)));
26467}
26468
26469#[track_caller]
26470fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
26471    editor
26472        .all_inlays(cx)
26473        .into_iter()
26474        .filter_map(|inlay| inlay.get_color())
26475        .map(Rgba::from)
26476        .collect()
26477}