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    },
   31    tree_sitter_python,
   32};
   33use language_settings::Formatter;
   34use languages::rust_lang;
   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;
   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_item_view::InvalidItemView,
   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!(
  224            editor.selections.ranges(&editor.display_snapshot(cx)),
  225            vec![4..4]
  226        );
  227
  228        editor.start_transaction_at(now, window, cx);
  229        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  230            s.select_ranges([4..5])
  231        });
  232        editor.insert("e", window, cx);
  233        editor.end_transaction_at(now, cx);
  234        assert_eq!(editor.text(cx), "12cde6");
  235        assert_eq!(
  236            editor.selections.ranges(&editor.display_snapshot(cx)),
  237            vec![5..5]
  238        );
  239
  240        now += group_interval + Duration::from_millis(1);
  241        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  242            s.select_ranges([2..2])
  243        });
  244
  245        // Simulate an edit in another editor
  246        buffer.update(cx, |buffer, cx| {
  247            buffer.start_transaction_at(now, cx);
  248            buffer.edit([(0..1, "a")], None, cx);
  249            buffer.edit([(1..1, "b")], None, cx);
  250            buffer.end_transaction_at(now, cx);
  251        });
  252
  253        assert_eq!(editor.text(cx), "ab2cde6");
  254        assert_eq!(
  255            editor.selections.ranges(&editor.display_snapshot(cx)),
  256            vec![3..3]
  257        );
  258
  259        // Last transaction happened past the group interval in a different editor.
  260        // Undo it individually and don't restore selections.
  261        editor.undo(&Undo, window, cx);
  262        assert_eq!(editor.text(cx), "12cde6");
  263        assert_eq!(
  264            editor.selections.ranges(&editor.display_snapshot(cx)),
  265            vec![2..2]
  266        );
  267
  268        // First two transactions happened within the group interval in this editor.
  269        // Undo them together and restore selections.
  270        editor.undo(&Undo, window, cx);
  271        editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
  272        assert_eq!(editor.text(cx), "123456");
  273        assert_eq!(
  274            editor.selections.ranges(&editor.display_snapshot(cx)),
  275            vec![0..0]
  276        );
  277
  278        // Redo the first two transactions together.
  279        editor.redo(&Redo, window, cx);
  280        assert_eq!(editor.text(cx), "12cde6");
  281        assert_eq!(
  282            editor.selections.ranges(&editor.display_snapshot(cx)),
  283            vec![5..5]
  284        );
  285
  286        // Redo the last transaction on its own.
  287        editor.redo(&Redo, window, cx);
  288        assert_eq!(editor.text(cx), "ab2cde6");
  289        assert_eq!(
  290            editor.selections.ranges(&editor.display_snapshot(cx)),
  291            vec![6..6]
  292        );
  293
  294        // Test empty transactions.
  295        editor.start_transaction_at(now, window, cx);
  296        editor.end_transaction_at(now, cx);
  297        editor.undo(&Undo, window, cx);
  298        assert_eq!(editor.text(cx), "12cde6");
  299    });
  300}
  301
  302#[gpui::test]
  303fn test_ime_composition(cx: &mut TestAppContext) {
  304    init_test(cx, |_| {});
  305
  306    let buffer = cx.new(|cx| {
  307        let mut buffer = language::Buffer::local("abcde", cx);
  308        // Ensure automatic grouping doesn't occur.
  309        buffer.set_group_interval(Duration::ZERO);
  310        buffer
  311    });
  312
  313    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  314    cx.add_window(|window, cx| {
  315        let mut editor = build_editor(buffer.clone(), window, cx);
  316
  317        // Start a new IME composition.
  318        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  319        editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx);
  320        editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
  321        assert_eq!(editor.text(cx), "äbcde");
  322        assert_eq!(
  323            editor.marked_text_ranges(cx),
  324            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  325        );
  326
  327        // Finalize IME composition.
  328        editor.replace_text_in_range(None, "ā", window, cx);
  329        assert_eq!(editor.text(cx), "ābcde");
  330        assert_eq!(editor.marked_text_ranges(cx), None);
  331
  332        // IME composition edits are grouped and are undone/redone at once.
  333        editor.undo(&Default::default(), window, cx);
  334        assert_eq!(editor.text(cx), "abcde");
  335        assert_eq!(editor.marked_text_ranges(cx), None);
  336        editor.redo(&Default::default(), window, cx);
  337        assert_eq!(editor.text(cx), "ābcde");
  338        assert_eq!(editor.marked_text_ranges(cx), None);
  339
  340        // Start a new IME composition.
  341        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  342        assert_eq!(
  343            editor.marked_text_ranges(cx),
  344            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  345        );
  346
  347        // Undoing during an IME composition cancels it.
  348        editor.undo(&Default::default(), window, cx);
  349        assert_eq!(editor.text(cx), "ābcde");
  350        assert_eq!(editor.marked_text_ranges(cx), None);
  351
  352        // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
  353        editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
  354        assert_eq!(editor.text(cx), "ābcdè");
  355        assert_eq!(
  356            editor.marked_text_ranges(cx),
  357            Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
  358        );
  359
  360        // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
  361        editor.replace_text_in_range(Some(4..999), "ę", window, cx);
  362        assert_eq!(editor.text(cx), "ābcdę");
  363        assert_eq!(editor.marked_text_ranges(cx), None);
  364
  365        // Start a new IME composition with multiple cursors.
  366        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  367            s.select_ranges([
  368                OffsetUtf16(1)..OffsetUtf16(1),
  369                OffsetUtf16(3)..OffsetUtf16(3),
  370                OffsetUtf16(5)..OffsetUtf16(5),
  371            ])
  372        });
  373        editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
  374        assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
  375        assert_eq!(
  376            editor.marked_text_ranges(cx),
  377            Some(vec![
  378                OffsetUtf16(0)..OffsetUtf16(3),
  379                OffsetUtf16(4)..OffsetUtf16(7),
  380                OffsetUtf16(8)..OffsetUtf16(11)
  381            ])
  382        );
  383
  384        // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
  385        editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
  386        assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
  387        assert_eq!(
  388            editor.marked_text_ranges(cx),
  389            Some(vec![
  390                OffsetUtf16(1)..OffsetUtf16(2),
  391                OffsetUtf16(5)..OffsetUtf16(6),
  392                OffsetUtf16(9)..OffsetUtf16(10)
  393            ])
  394        );
  395
  396        // Finalize IME composition with multiple cursors.
  397        editor.replace_text_in_range(Some(9..10), "2", window, cx);
  398        assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
  399        assert_eq!(editor.marked_text_ranges(cx), None);
  400
  401        editor
  402    });
  403}
  404
  405#[gpui::test]
  406fn test_selection_with_mouse(cx: &mut TestAppContext) {
  407    init_test(cx, |_| {});
  408
  409    let editor = cx.add_window(|window, cx| {
  410        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  411        build_editor(buffer, window, cx)
  412    });
  413
  414    _ = editor.update(cx, |editor, window, cx| {
  415        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  416    });
  417    assert_eq!(
  418        editor
  419            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  420            .unwrap(),
  421        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  422    );
  423
  424    _ = editor.update(cx, |editor, window, cx| {
  425        editor.update_selection(
  426            DisplayPoint::new(DisplayRow(3), 3),
  427            0,
  428            gpui::Point::<f32>::default(),
  429            window,
  430            cx,
  431        );
  432    });
  433
  434    assert_eq!(
  435        editor
  436            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  437            .unwrap(),
  438        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  439    );
  440
  441    _ = editor.update(cx, |editor, window, cx| {
  442        editor.update_selection(
  443            DisplayPoint::new(DisplayRow(1), 1),
  444            0,
  445            gpui::Point::<f32>::default(),
  446            window,
  447            cx,
  448        );
  449    });
  450
  451    assert_eq!(
  452        editor
  453            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  454            .unwrap(),
  455        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  456    );
  457
  458    _ = editor.update(cx, |editor, window, cx| {
  459        editor.end_selection(window, cx);
  460        editor.update_selection(
  461            DisplayPoint::new(DisplayRow(3), 3),
  462            0,
  463            gpui::Point::<f32>::default(),
  464            window,
  465            cx,
  466        );
  467    });
  468
  469    assert_eq!(
  470        editor
  471            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  472            .unwrap(),
  473        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  474    );
  475
  476    _ = editor.update(cx, |editor, window, cx| {
  477        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
  478        editor.update_selection(
  479            DisplayPoint::new(DisplayRow(0), 0),
  480            0,
  481            gpui::Point::<f32>::default(),
  482            window,
  483            cx,
  484        );
  485    });
  486
  487    assert_eq!(
  488        editor
  489            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  490            .unwrap(),
  491        [
  492            DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
  493            DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
  494        ]
  495    );
  496
  497    _ = editor.update(cx, |editor, window, cx| {
  498        editor.end_selection(window, cx);
  499    });
  500
  501    assert_eq!(
  502        editor
  503            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  504            .unwrap(),
  505        [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
  506    );
  507}
  508
  509#[gpui::test]
  510fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
  511    init_test(cx, |_| {});
  512
  513    let editor = cx.add_window(|window, cx| {
  514        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  515        build_editor(buffer, window, cx)
  516    });
  517
  518    _ = editor.update(cx, |editor, window, cx| {
  519        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
  520    });
  521
  522    _ = editor.update(cx, |editor, window, cx| {
  523        editor.end_selection(window, cx);
  524    });
  525
  526    _ = editor.update(cx, |editor, window, cx| {
  527        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
  528    });
  529
  530    _ = editor.update(cx, |editor, window, cx| {
  531        editor.end_selection(window, cx);
  532    });
  533
  534    assert_eq!(
  535        editor
  536            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  537            .unwrap(),
  538        [
  539            DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
  540            DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
  541        ]
  542    );
  543
  544    _ = editor.update(cx, |editor, window, cx| {
  545        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
  546    });
  547
  548    _ = editor.update(cx, |editor, window, cx| {
  549        editor.end_selection(window, cx);
  550    });
  551
  552    assert_eq!(
  553        editor
  554            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  555            .unwrap(),
  556        [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  557    );
  558}
  559
  560#[gpui::test]
  561fn test_canceling_pending_selection(cx: &mut TestAppContext) {
  562    init_test(cx, |_| {});
  563
  564    let editor = cx.add_window(|window, cx| {
  565        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  566        build_editor(buffer, window, cx)
  567    });
  568
  569    _ = editor.update(cx, |editor, window, cx| {
  570        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  571        assert_eq!(
  572            editor.selections.display_ranges(cx),
  573            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  574        );
  575    });
  576
  577    _ = editor.update(cx, |editor, window, cx| {
  578        editor.update_selection(
  579            DisplayPoint::new(DisplayRow(3), 3),
  580            0,
  581            gpui::Point::<f32>::default(),
  582            window,
  583            cx,
  584        );
  585        assert_eq!(
  586            editor.selections.display_ranges(cx),
  587            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  588        );
  589    });
  590
  591    _ = editor.update(cx, |editor, window, cx| {
  592        editor.cancel(&Cancel, window, cx);
  593        editor.update_selection(
  594            DisplayPoint::new(DisplayRow(1), 1),
  595            0,
  596            gpui::Point::<f32>::default(),
  597            window,
  598            cx,
  599        );
  600        assert_eq!(
  601            editor.selections.display_ranges(cx),
  602            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  603        );
  604    });
  605}
  606
  607#[gpui::test]
  608fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
  609    init_test(cx, |_| {});
  610
  611    let editor = cx.add_window(|window, cx| {
  612        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  613        build_editor(buffer, window, cx)
  614    });
  615
  616    _ = editor.update(cx, |editor, window, cx| {
  617        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  618        assert_eq!(
  619            editor.selections.display_ranges(cx),
  620            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  621        );
  622
  623        editor.move_down(&Default::default(), window, cx);
  624        assert_eq!(
  625            editor.selections.display_ranges(cx),
  626            [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  627        );
  628
  629        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  630        assert_eq!(
  631            editor.selections.display_ranges(cx),
  632            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  633        );
  634
  635        editor.move_up(&Default::default(), window, cx);
  636        assert_eq!(
  637            editor.selections.display_ranges(cx),
  638            [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
  639        );
  640    });
  641}
  642
  643#[gpui::test]
  644fn test_extending_selection(cx: &mut TestAppContext) {
  645    init_test(cx, |_| {});
  646
  647    let editor = cx.add_window(|window, cx| {
  648        let buffer = MultiBuffer::build_simple("aaa bbb ccc ddd eee", cx);
  649        build_editor(buffer, window, cx)
  650    });
  651
  652    _ = editor.update(cx, |editor, window, cx| {
  653        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), false, 1, window, cx);
  654        editor.end_selection(window, cx);
  655        assert_eq!(
  656            editor.selections.display_ranges(cx),
  657            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)]
  658        );
  659
  660        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  661        editor.end_selection(window, cx);
  662        assert_eq!(
  663            editor.selections.display_ranges(cx),
  664            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 10)]
  665        );
  666
  667        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  668        editor.end_selection(window, cx);
  669        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 2, window, cx);
  670        assert_eq!(
  671            editor.selections.display_ranges(cx),
  672            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 11)]
  673        );
  674
  675        editor.update_selection(
  676            DisplayPoint::new(DisplayRow(0), 1),
  677            0,
  678            gpui::Point::<f32>::default(),
  679            window,
  680            cx,
  681        );
  682        editor.end_selection(window, cx);
  683        assert_eq!(
  684            editor.selections.display_ranges(cx),
  685            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 0)]
  686        );
  687
  688        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), true, 1, window, cx);
  689        editor.end_selection(window, cx);
  690        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), true, 2, window, cx);
  691        editor.end_selection(window, cx);
  692        assert_eq!(
  693            editor.selections.display_ranges(cx),
  694            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 7)]
  695        );
  696
  697        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  698        assert_eq!(
  699            editor.selections.display_ranges(cx),
  700            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 11)]
  701        );
  702
  703        editor.update_selection(
  704            DisplayPoint::new(DisplayRow(0), 6),
  705            0,
  706            gpui::Point::<f32>::default(),
  707            window,
  708            cx,
  709        );
  710        assert_eq!(
  711            editor.selections.display_ranges(cx),
  712            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 7)]
  713        );
  714
  715        editor.update_selection(
  716            DisplayPoint::new(DisplayRow(0), 1),
  717            0,
  718            gpui::Point::<f32>::default(),
  719            window,
  720            cx,
  721        );
  722        editor.end_selection(window, cx);
  723        assert_eq!(
  724            editor.selections.display_ranges(cx),
  725            [DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 0)]
  726        );
  727    });
  728}
  729
  730#[gpui::test]
  731fn test_clone(cx: &mut TestAppContext) {
  732    init_test(cx, |_| {});
  733
  734    let (text, selection_ranges) = marked_text_ranges(
  735        indoc! {"
  736            one
  737            two
  738            threeˇ
  739            four
  740            fiveˇ
  741        "},
  742        true,
  743    );
  744
  745    let editor = cx.add_window(|window, cx| {
  746        let buffer = MultiBuffer::build_simple(&text, cx);
  747        build_editor(buffer, window, cx)
  748    });
  749
  750    _ = editor.update(cx, |editor, window, cx| {
  751        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  752            s.select_ranges(selection_ranges.clone())
  753        });
  754        editor.fold_creases(
  755            vec![
  756                Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
  757                Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
  758            ],
  759            true,
  760            window,
  761            cx,
  762        );
  763    });
  764
  765    let cloned_editor = editor
  766        .update(cx, |editor, _, cx| {
  767            cx.open_window(Default::default(), |window, cx| {
  768                cx.new(|cx| editor.clone(window, cx))
  769            })
  770        })
  771        .unwrap()
  772        .unwrap();
  773
  774    let snapshot = editor
  775        .update(cx, |e, window, cx| e.snapshot(window, cx))
  776        .unwrap();
  777    let cloned_snapshot = cloned_editor
  778        .update(cx, |e, window, cx| e.snapshot(window, cx))
  779        .unwrap();
  780
  781    assert_eq!(
  782        cloned_editor
  783            .update(cx, |e, _, cx| e.display_text(cx))
  784            .unwrap(),
  785        editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
  786    );
  787    assert_eq!(
  788        cloned_snapshot
  789            .folds_in_range(0..text.len())
  790            .collect::<Vec<_>>(),
  791        snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
  792    );
  793    assert_set_eq!(
  794        cloned_editor
  795            .update(cx, |editor, _, cx| editor
  796                .selections
  797                .ranges::<Point>(&editor.display_snapshot(cx)))
  798            .unwrap(),
  799        editor
  800            .update(cx, |editor, _, cx| editor
  801                .selections
  802                .ranges(&editor.display_snapshot(cx)))
  803            .unwrap()
  804    );
  805    assert_set_eq!(
  806        cloned_editor
  807            .update(cx, |e, _window, cx| e.selections.display_ranges(cx))
  808            .unwrap(),
  809        editor
  810            .update(cx, |e, _, cx| e.selections.display_ranges(cx))
  811            .unwrap()
  812    );
  813}
  814
  815#[gpui::test]
  816async fn test_navigation_history(cx: &mut TestAppContext) {
  817    init_test(cx, |_| {});
  818
  819    use workspace::item::Item;
  820
  821    let fs = FakeFs::new(cx.executor());
  822    let project = Project::test(fs, [], cx).await;
  823    let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
  824    let pane = workspace
  825        .update(cx, |workspace, _, _| workspace.active_pane().clone())
  826        .unwrap();
  827
  828    _ = workspace.update(cx, |_v, window, cx| {
  829        cx.new(|cx| {
  830            let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
  831            let mut editor = build_editor(buffer, window, cx);
  832            let handle = cx.entity();
  833            editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
  834
  835            fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
  836                editor.nav_history.as_mut().unwrap().pop_backward(cx)
  837            }
  838
  839            // Move the cursor a small distance.
  840            // Nothing is added to the navigation history.
  841            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  842                s.select_display_ranges([
  843                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
  844                ])
  845            });
  846            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  847                s.select_display_ranges([
  848                    DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
  849                ])
  850            });
  851            assert!(pop_history(&mut editor, cx).is_none());
  852
  853            // Move the cursor a large distance.
  854            // The history can jump back to the previous position.
  855            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  856                s.select_display_ranges([
  857                    DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
  858                ])
  859            });
  860            let nav_entry = pop_history(&mut editor, cx).unwrap();
  861            editor.navigate(nav_entry.data.unwrap(), window, cx);
  862            assert_eq!(nav_entry.item.id(), cx.entity_id());
  863            assert_eq!(
  864                editor.selections.display_ranges(cx),
  865                &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
  866            );
  867            assert!(pop_history(&mut editor, cx).is_none());
  868
  869            // Move the cursor a small distance via the mouse.
  870            // Nothing is added to the navigation history.
  871            editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
  872            editor.end_selection(window, cx);
  873            assert_eq!(
  874                editor.selections.display_ranges(cx),
  875                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  876            );
  877            assert!(pop_history(&mut editor, cx).is_none());
  878
  879            // Move the cursor a large distance via the mouse.
  880            // The history can jump back to the previous position.
  881            editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
  882            editor.end_selection(window, cx);
  883            assert_eq!(
  884                editor.selections.display_ranges(cx),
  885                &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
  886            );
  887            let nav_entry = pop_history(&mut editor, cx).unwrap();
  888            editor.navigate(nav_entry.data.unwrap(), window, cx);
  889            assert_eq!(nav_entry.item.id(), cx.entity_id());
  890            assert_eq!(
  891                editor.selections.display_ranges(cx),
  892                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  893            );
  894            assert!(pop_history(&mut editor, cx).is_none());
  895
  896            // Set scroll position to check later
  897            editor.set_scroll_position(gpui::Point::<f64>::new(5.5, 5.5), window, cx);
  898            let original_scroll_position = editor.scroll_manager.anchor();
  899
  900            // Jump to the end of the document and adjust scroll
  901            editor.move_to_end(&MoveToEnd, window, cx);
  902            editor.set_scroll_position(gpui::Point::<f64>::new(-2.5, -0.5), window, cx);
  903            assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
  904
  905            let nav_entry = pop_history(&mut editor, cx).unwrap();
  906            editor.navigate(nav_entry.data.unwrap(), window, cx);
  907            assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
  908
  909            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
  910            let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
  911            invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
  912            let invalid_point = Point::new(9999, 0);
  913            editor.navigate(
  914                Box::new(NavigationData {
  915                    cursor_anchor: invalid_anchor,
  916                    cursor_position: invalid_point,
  917                    scroll_anchor: ScrollAnchor {
  918                        anchor: invalid_anchor,
  919                        offset: Default::default(),
  920                    },
  921                    scroll_top_row: invalid_point.row,
  922                }),
  923                window,
  924                cx,
  925            );
  926            assert_eq!(
  927                editor.selections.display_ranges(cx),
  928                &[editor.max_point(cx)..editor.max_point(cx)]
  929            );
  930            assert_eq!(
  931                editor.scroll_position(cx),
  932                gpui::Point::new(0., editor.max_point(cx).row().as_f64())
  933            );
  934
  935            editor
  936        })
  937    });
  938}
  939
  940#[gpui::test]
  941fn test_cancel(cx: &mut TestAppContext) {
  942    init_test(cx, |_| {});
  943
  944    let editor = cx.add_window(|window, cx| {
  945        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  946        build_editor(buffer, window, cx)
  947    });
  948
  949    _ = editor.update(cx, |editor, window, cx| {
  950        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
  951        editor.update_selection(
  952            DisplayPoint::new(DisplayRow(1), 1),
  953            0,
  954            gpui::Point::<f32>::default(),
  955            window,
  956            cx,
  957        );
  958        editor.end_selection(window, cx);
  959
  960        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
  961        editor.update_selection(
  962            DisplayPoint::new(DisplayRow(0), 3),
  963            0,
  964            gpui::Point::<f32>::default(),
  965            window,
  966            cx,
  967        );
  968        editor.end_selection(window, cx);
  969        assert_eq!(
  970            editor.selections.display_ranges(cx),
  971            [
  972                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
  973                DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
  974            ]
  975        );
  976    });
  977
  978    _ = editor.update(cx, |editor, window, cx| {
  979        editor.cancel(&Cancel, window, cx);
  980        assert_eq!(
  981            editor.selections.display_ranges(cx),
  982            [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
  983        );
  984    });
  985
  986    _ = editor.update(cx, |editor, window, cx| {
  987        editor.cancel(&Cancel, window, cx);
  988        assert_eq!(
  989            editor.selections.display_ranges(cx),
  990            [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
  991        );
  992    });
  993}
  994
  995#[gpui::test]
  996fn test_fold_action(cx: &mut TestAppContext) {
  997    init_test(cx, |_| {});
  998
  999    let editor = cx.add_window(|window, cx| {
 1000        let buffer = MultiBuffer::build_simple(
 1001            &"
 1002                impl Foo {
 1003                    // Hello!
 1004
 1005                    fn a() {
 1006                        1
 1007                    }
 1008
 1009                    fn b() {
 1010                        2
 1011                    }
 1012
 1013                    fn c() {
 1014                        3
 1015                    }
 1016                }
 1017            "
 1018            .unindent(),
 1019            cx,
 1020        );
 1021        build_editor(buffer, window, cx)
 1022    });
 1023
 1024    _ = editor.update(cx, |editor, window, cx| {
 1025        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1026            s.select_display_ranges([
 1027                DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
 1028            ]);
 1029        });
 1030        editor.fold(&Fold, window, cx);
 1031        assert_eq!(
 1032            editor.display_text(cx),
 1033            "
 1034                impl Foo {
 1035                    // Hello!
 1036
 1037                    fn a() {
 1038                        1
 1039                    }
 1040
 1041                    fn b() {⋯
 1042                    }
 1043
 1044                    fn c() {⋯
 1045                    }
 1046                }
 1047            "
 1048            .unindent(),
 1049        );
 1050
 1051        editor.fold(&Fold, window, cx);
 1052        assert_eq!(
 1053            editor.display_text(cx),
 1054            "
 1055                impl Foo {⋯
 1056                }
 1057            "
 1058            .unindent(),
 1059        );
 1060
 1061        editor.unfold_lines(&UnfoldLines, window, cx);
 1062        assert_eq!(
 1063            editor.display_text(cx),
 1064            "
 1065                impl Foo {
 1066                    // Hello!
 1067
 1068                    fn a() {
 1069                        1
 1070                    }
 1071
 1072                    fn b() {⋯
 1073                    }
 1074
 1075                    fn c() {⋯
 1076                    }
 1077                }
 1078            "
 1079            .unindent(),
 1080        );
 1081
 1082        editor.unfold_lines(&UnfoldLines, window, cx);
 1083        assert_eq!(
 1084            editor.display_text(cx),
 1085            editor.buffer.read(cx).read(cx).text()
 1086        );
 1087    });
 1088}
 1089
 1090#[gpui::test]
 1091fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
 1092    init_test(cx, |_| {});
 1093
 1094    let editor = cx.add_window(|window, cx| {
 1095        let buffer = MultiBuffer::build_simple(
 1096            &"
 1097                class Foo:
 1098                    # Hello!
 1099
 1100                    def a():
 1101                        print(1)
 1102
 1103                    def b():
 1104                        print(2)
 1105
 1106                    def c():
 1107                        print(3)
 1108            "
 1109            .unindent(),
 1110            cx,
 1111        );
 1112        build_editor(buffer, window, cx)
 1113    });
 1114
 1115    _ = editor.update(cx, |editor, window, cx| {
 1116        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1117            s.select_display_ranges([
 1118                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
 1119            ]);
 1120        });
 1121        editor.fold(&Fold, window, cx);
 1122        assert_eq!(
 1123            editor.display_text(cx),
 1124            "
 1125                class Foo:
 1126                    # Hello!
 1127
 1128                    def a():
 1129                        print(1)
 1130
 1131                    def b():⋯
 1132
 1133                    def c():⋯
 1134            "
 1135            .unindent(),
 1136        );
 1137
 1138        editor.fold(&Fold, window, cx);
 1139        assert_eq!(
 1140            editor.display_text(cx),
 1141            "
 1142                class Foo:⋯
 1143            "
 1144            .unindent(),
 1145        );
 1146
 1147        editor.unfold_lines(&UnfoldLines, window, cx);
 1148        assert_eq!(
 1149            editor.display_text(cx),
 1150            "
 1151                class Foo:
 1152                    # Hello!
 1153
 1154                    def a():
 1155                        print(1)
 1156
 1157                    def b():⋯
 1158
 1159                    def c():⋯
 1160            "
 1161            .unindent(),
 1162        );
 1163
 1164        editor.unfold_lines(&UnfoldLines, window, cx);
 1165        assert_eq!(
 1166            editor.display_text(cx),
 1167            editor.buffer.read(cx).read(cx).text()
 1168        );
 1169    });
 1170}
 1171
 1172#[gpui::test]
 1173fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
 1174    init_test(cx, |_| {});
 1175
 1176    let editor = cx.add_window(|window, cx| {
 1177        let buffer = MultiBuffer::build_simple(
 1178            &"
 1179                class Foo:
 1180                    # Hello!
 1181
 1182                    def a():
 1183                        print(1)
 1184
 1185                    def b():
 1186                        print(2)
 1187
 1188
 1189                    def c():
 1190                        print(3)
 1191
 1192
 1193            "
 1194            .unindent(),
 1195            cx,
 1196        );
 1197        build_editor(buffer, window, cx)
 1198    });
 1199
 1200    _ = editor.update(cx, |editor, window, cx| {
 1201        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1202            s.select_display_ranges([
 1203                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
 1204            ]);
 1205        });
 1206        editor.fold(&Fold, window, cx);
 1207        assert_eq!(
 1208            editor.display_text(cx),
 1209            "
 1210                class Foo:
 1211                    # Hello!
 1212
 1213                    def a():
 1214                        print(1)
 1215
 1216                    def b():⋯
 1217
 1218
 1219                    def c():⋯
 1220
 1221
 1222            "
 1223            .unindent(),
 1224        );
 1225
 1226        editor.fold(&Fold, window, cx);
 1227        assert_eq!(
 1228            editor.display_text(cx),
 1229            "
 1230                class Foo:⋯
 1231
 1232
 1233            "
 1234            .unindent(),
 1235        );
 1236
 1237        editor.unfold_lines(&UnfoldLines, window, cx);
 1238        assert_eq!(
 1239            editor.display_text(cx),
 1240            "
 1241                class Foo:
 1242                    # Hello!
 1243
 1244                    def a():
 1245                        print(1)
 1246
 1247                    def b():⋯
 1248
 1249
 1250                    def c():⋯
 1251
 1252
 1253            "
 1254            .unindent(),
 1255        );
 1256
 1257        editor.unfold_lines(&UnfoldLines, window, cx);
 1258        assert_eq!(
 1259            editor.display_text(cx),
 1260            editor.buffer.read(cx).read(cx).text()
 1261        );
 1262    });
 1263}
 1264
 1265#[gpui::test]
 1266fn test_fold_at_level(cx: &mut TestAppContext) {
 1267    init_test(cx, |_| {});
 1268
 1269    let editor = cx.add_window(|window, cx| {
 1270        let buffer = MultiBuffer::build_simple(
 1271            &"
 1272                class Foo:
 1273                    # Hello!
 1274
 1275                    def a():
 1276                        print(1)
 1277
 1278                    def b():
 1279                        print(2)
 1280
 1281
 1282                class Bar:
 1283                    # World!
 1284
 1285                    def a():
 1286                        print(1)
 1287
 1288                    def b():
 1289                        print(2)
 1290
 1291
 1292            "
 1293            .unindent(),
 1294            cx,
 1295        );
 1296        build_editor(buffer, window, cx)
 1297    });
 1298
 1299    _ = editor.update(cx, |editor, window, cx| {
 1300        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1301        assert_eq!(
 1302            editor.display_text(cx),
 1303            "
 1304                class Foo:
 1305                    # Hello!
 1306
 1307                    def a():⋯
 1308
 1309                    def b():⋯
 1310
 1311
 1312                class Bar:
 1313                    # World!
 1314
 1315                    def a():⋯
 1316
 1317                    def b():⋯
 1318
 1319
 1320            "
 1321            .unindent(),
 1322        );
 1323
 1324        editor.fold_at_level(&FoldAtLevel(1), window, cx);
 1325        assert_eq!(
 1326            editor.display_text(cx),
 1327            "
 1328                class Foo:⋯
 1329
 1330
 1331                class Bar:⋯
 1332
 1333
 1334            "
 1335            .unindent(),
 1336        );
 1337
 1338        editor.unfold_all(&UnfoldAll, window, cx);
 1339        editor.fold_at_level(&FoldAtLevel(0), window, cx);
 1340        assert_eq!(
 1341            editor.display_text(cx),
 1342            "
 1343                class Foo:
 1344                    # Hello!
 1345
 1346                    def a():
 1347                        print(1)
 1348
 1349                    def b():
 1350                        print(2)
 1351
 1352
 1353                class Bar:
 1354                    # World!
 1355
 1356                    def a():
 1357                        print(1)
 1358
 1359                    def b():
 1360                        print(2)
 1361
 1362
 1363            "
 1364            .unindent(),
 1365        );
 1366
 1367        assert_eq!(
 1368            editor.display_text(cx),
 1369            editor.buffer.read(cx).read(cx).text()
 1370        );
 1371        let (_, positions) = marked_text_ranges(
 1372            &"
 1373                       class Foo:
 1374                           # Hello!
 1375
 1376                           def a():
 1377                              print(1)
 1378
 1379                           def b():
 1380                               p«riˇ»nt(2)
 1381
 1382
 1383                       class Bar:
 1384                           # World!
 1385
 1386                           def a():
 1387                               «ˇprint(1)
 1388
 1389                           def b():
 1390                               print(2)»
 1391
 1392
 1393                   "
 1394            .unindent(),
 1395            true,
 1396        );
 1397
 1398        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 1399            s.select_ranges(positions)
 1400        });
 1401
 1402        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1403        assert_eq!(
 1404            editor.display_text(cx),
 1405            "
 1406                class Foo:
 1407                    # Hello!
 1408
 1409                    def a():⋯
 1410
 1411                    def b():
 1412                        print(2)
 1413
 1414
 1415                class Bar:
 1416                    # World!
 1417
 1418                    def a():
 1419                        print(1)
 1420
 1421                    def b():
 1422                        print(2)
 1423
 1424
 1425            "
 1426            .unindent(),
 1427        );
 1428    });
 1429}
 1430
 1431#[gpui::test]
 1432fn test_move_cursor(cx: &mut TestAppContext) {
 1433    init_test(cx, |_| {});
 1434
 1435    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
 1436    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
 1437
 1438    buffer.update(cx, |buffer, cx| {
 1439        buffer.edit(
 1440            vec![
 1441                (Point::new(1, 0)..Point::new(1, 0), "\t"),
 1442                (Point::new(1, 1)..Point::new(1, 1), "\t"),
 1443            ],
 1444            None,
 1445            cx,
 1446        );
 1447    });
 1448    _ = editor.update(cx, |editor, window, cx| {
 1449        assert_eq!(
 1450            editor.selections.display_ranges(cx),
 1451            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1452        );
 1453
 1454        editor.move_down(&MoveDown, window, cx);
 1455        assert_eq!(
 1456            editor.selections.display_ranges(cx),
 1457            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1458        );
 1459
 1460        editor.move_right(&MoveRight, window, cx);
 1461        assert_eq!(
 1462            editor.selections.display_ranges(cx),
 1463            &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
 1464        );
 1465
 1466        editor.move_left(&MoveLeft, window, cx);
 1467        assert_eq!(
 1468            editor.selections.display_ranges(cx),
 1469            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1470        );
 1471
 1472        editor.move_up(&MoveUp, window, cx);
 1473        assert_eq!(
 1474            editor.selections.display_ranges(cx),
 1475            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1476        );
 1477
 1478        editor.move_to_end(&MoveToEnd, window, cx);
 1479        assert_eq!(
 1480            editor.selections.display_ranges(cx),
 1481            &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
 1482        );
 1483
 1484        editor.move_to_beginning(&MoveToBeginning, window, cx);
 1485        assert_eq!(
 1486            editor.selections.display_ranges(cx),
 1487            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1488        );
 1489
 1490        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1491            s.select_display_ranges([
 1492                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
 1493            ]);
 1494        });
 1495        editor.select_to_beginning(&SelectToBeginning, window, cx);
 1496        assert_eq!(
 1497            editor.selections.display_ranges(cx),
 1498            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
 1499        );
 1500
 1501        editor.select_to_end(&SelectToEnd, window, cx);
 1502        assert_eq!(
 1503            editor.selections.display_ranges(cx),
 1504            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
 1505        );
 1506    });
 1507}
 1508
 1509#[gpui::test]
 1510fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 1511    init_test(cx, |_| {});
 1512
 1513    let editor = cx.add_window(|window, cx| {
 1514        let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
 1515        build_editor(buffer, window, cx)
 1516    });
 1517
 1518    assert_eq!('🟥'.len_utf8(), 4);
 1519    assert_eq!('α'.len_utf8(), 2);
 1520
 1521    _ = editor.update(cx, |editor, window, cx| {
 1522        editor.fold_creases(
 1523            vec![
 1524                Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
 1525                Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
 1526                Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
 1527            ],
 1528            true,
 1529            window,
 1530            cx,
 1531        );
 1532        assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
 1533
 1534        editor.move_right(&MoveRight, window, cx);
 1535        assert_eq!(
 1536            editor.selections.display_ranges(cx),
 1537            &[empty_range(0, "🟥".len())]
 1538        );
 1539        editor.move_right(&MoveRight, window, cx);
 1540        assert_eq!(
 1541            editor.selections.display_ranges(cx),
 1542            &[empty_range(0, "🟥🟧".len())]
 1543        );
 1544        editor.move_right(&MoveRight, window, cx);
 1545        assert_eq!(
 1546            editor.selections.display_ranges(cx),
 1547            &[empty_range(0, "🟥🟧⋯".len())]
 1548        );
 1549
 1550        editor.move_down(&MoveDown, window, cx);
 1551        assert_eq!(
 1552            editor.selections.display_ranges(cx),
 1553            &[empty_range(1, "ab⋯e".len())]
 1554        );
 1555        editor.move_left(&MoveLeft, window, cx);
 1556        assert_eq!(
 1557            editor.selections.display_ranges(cx),
 1558            &[empty_range(1, "ab⋯".len())]
 1559        );
 1560        editor.move_left(&MoveLeft, window, cx);
 1561        assert_eq!(
 1562            editor.selections.display_ranges(cx),
 1563            &[empty_range(1, "ab".len())]
 1564        );
 1565        editor.move_left(&MoveLeft, window, cx);
 1566        assert_eq!(
 1567            editor.selections.display_ranges(cx),
 1568            &[empty_range(1, "a".len())]
 1569        );
 1570
 1571        editor.move_down(&MoveDown, window, cx);
 1572        assert_eq!(
 1573            editor.selections.display_ranges(cx),
 1574            &[empty_range(2, "α".len())]
 1575        );
 1576        editor.move_right(&MoveRight, window, cx);
 1577        assert_eq!(
 1578            editor.selections.display_ranges(cx),
 1579            &[empty_range(2, "αβ".len())]
 1580        );
 1581        editor.move_right(&MoveRight, window, cx);
 1582        assert_eq!(
 1583            editor.selections.display_ranges(cx),
 1584            &[empty_range(2, "αβ⋯".len())]
 1585        );
 1586        editor.move_right(&MoveRight, window, cx);
 1587        assert_eq!(
 1588            editor.selections.display_ranges(cx),
 1589            &[empty_range(2, "αβ⋯ε".len())]
 1590        );
 1591
 1592        editor.move_up(&MoveUp, window, cx);
 1593        assert_eq!(
 1594            editor.selections.display_ranges(cx),
 1595            &[empty_range(1, "ab⋯e".len())]
 1596        );
 1597        editor.move_down(&MoveDown, window, cx);
 1598        assert_eq!(
 1599            editor.selections.display_ranges(cx),
 1600            &[empty_range(2, "αβ⋯ε".len())]
 1601        );
 1602        editor.move_up(&MoveUp, window, cx);
 1603        assert_eq!(
 1604            editor.selections.display_ranges(cx),
 1605            &[empty_range(1, "ab⋯e".len())]
 1606        );
 1607
 1608        editor.move_up(&MoveUp, window, cx);
 1609        assert_eq!(
 1610            editor.selections.display_ranges(cx),
 1611            &[empty_range(0, "🟥🟧".len())]
 1612        );
 1613        editor.move_left(&MoveLeft, window, cx);
 1614        assert_eq!(
 1615            editor.selections.display_ranges(cx),
 1616            &[empty_range(0, "🟥".len())]
 1617        );
 1618        editor.move_left(&MoveLeft, window, cx);
 1619        assert_eq!(
 1620            editor.selections.display_ranges(cx),
 1621            &[empty_range(0, "".len())]
 1622        );
 1623    });
 1624}
 1625
 1626#[gpui::test]
 1627fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
 1628    init_test(cx, |_| {});
 1629
 1630    let editor = cx.add_window(|window, cx| {
 1631        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
 1632        build_editor(buffer, window, cx)
 1633    });
 1634    _ = editor.update(cx, |editor, window, cx| {
 1635        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1636            s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
 1637        });
 1638
 1639        // moving above start of document should move selection to start of document,
 1640        // but the next move down should still be at the original goal_x
 1641        editor.move_up(&MoveUp, window, cx);
 1642        assert_eq!(
 1643            editor.selections.display_ranges(cx),
 1644            &[empty_range(0, "".len())]
 1645        );
 1646
 1647        editor.move_down(&MoveDown, window, cx);
 1648        assert_eq!(
 1649            editor.selections.display_ranges(cx),
 1650            &[empty_range(1, "abcd".len())]
 1651        );
 1652
 1653        editor.move_down(&MoveDown, window, cx);
 1654        assert_eq!(
 1655            editor.selections.display_ranges(cx),
 1656            &[empty_range(2, "αβγ".len())]
 1657        );
 1658
 1659        editor.move_down(&MoveDown, window, cx);
 1660        assert_eq!(
 1661            editor.selections.display_ranges(cx),
 1662            &[empty_range(3, "abcd".len())]
 1663        );
 1664
 1665        editor.move_down(&MoveDown, window, cx);
 1666        assert_eq!(
 1667            editor.selections.display_ranges(cx),
 1668            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1669        );
 1670
 1671        // moving past end of document should not change goal_x
 1672        editor.move_down(&MoveDown, window, cx);
 1673        assert_eq!(
 1674            editor.selections.display_ranges(cx),
 1675            &[empty_range(5, "".len())]
 1676        );
 1677
 1678        editor.move_down(&MoveDown, window, cx);
 1679        assert_eq!(
 1680            editor.selections.display_ranges(cx),
 1681            &[empty_range(5, "".len())]
 1682        );
 1683
 1684        editor.move_up(&MoveUp, window, cx);
 1685        assert_eq!(
 1686            editor.selections.display_ranges(cx),
 1687            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1688        );
 1689
 1690        editor.move_up(&MoveUp, window, cx);
 1691        assert_eq!(
 1692            editor.selections.display_ranges(cx),
 1693            &[empty_range(3, "abcd".len())]
 1694        );
 1695
 1696        editor.move_up(&MoveUp, window, cx);
 1697        assert_eq!(
 1698            editor.selections.display_ranges(cx),
 1699            &[empty_range(2, "αβγ".len())]
 1700        );
 1701    });
 1702}
 1703
 1704#[gpui::test]
 1705fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 1706    init_test(cx, |_| {});
 1707    let move_to_beg = MoveToBeginningOfLine {
 1708        stop_at_soft_wraps: true,
 1709        stop_at_indent: true,
 1710    };
 1711
 1712    let delete_to_beg = DeleteToBeginningOfLine {
 1713        stop_at_indent: false,
 1714    };
 1715
 1716    let move_to_end = MoveToEndOfLine {
 1717        stop_at_soft_wraps: true,
 1718    };
 1719
 1720    let editor = cx.add_window(|window, cx| {
 1721        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1722        build_editor(buffer, window, cx)
 1723    });
 1724    _ = editor.update(cx, |editor, window, cx| {
 1725        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1726            s.select_display_ranges([
 1727                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1728                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1729            ]);
 1730        });
 1731    });
 1732
 1733    _ = editor.update(cx, |editor, window, cx| {
 1734        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1735        assert_eq!(
 1736            editor.selections.display_ranges(cx),
 1737            &[
 1738                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1739                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1740            ]
 1741        );
 1742    });
 1743
 1744    _ = editor.update(cx, |editor, window, cx| {
 1745        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1746        assert_eq!(
 1747            editor.selections.display_ranges(cx),
 1748            &[
 1749                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1750                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1751            ]
 1752        );
 1753    });
 1754
 1755    _ = editor.update(cx, |editor, window, cx| {
 1756        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1757        assert_eq!(
 1758            editor.selections.display_ranges(cx),
 1759            &[
 1760                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1761                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1762            ]
 1763        );
 1764    });
 1765
 1766    _ = editor.update(cx, |editor, window, cx| {
 1767        editor.move_to_end_of_line(&move_to_end, window, cx);
 1768        assert_eq!(
 1769            editor.selections.display_ranges(cx),
 1770            &[
 1771                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1772                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1773            ]
 1774        );
 1775    });
 1776
 1777    // Moving to the end of line again is a no-op.
 1778    _ = editor.update(cx, |editor, window, cx| {
 1779        editor.move_to_end_of_line(&move_to_end, window, cx);
 1780        assert_eq!(
 1781            editor.selections.display_ranges(cx),
 1782            &[
 1783                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1784                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1785            ]
 1786        );
 1787    });
 1788
 1789    _ = editor.update(cx, |editor, window, cx| {
 1790        editor.move_left(&MoveLeft, window, cx);
 1791        editor.select_to_beginning_of_line(
 1792            &SelectToBeginningOfLine {
 1793                stop_at_soft_wraps: true,
 1794                stop_at_indent: true,
 1795            },
 1796            window,
 1797            cx,
 1798        );
 1799        assert_eq!(
 1800            editor.selections.display_ranges(cx),
 1801            &[
 1802                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1803                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1804            ]
 1805        );
 1806    });
 1807
 1808    _ = editor.update(cx, |editor, window, cx| {
 1809        editor.select_to_beginning_of_line(
 1810            &SelectToBeginningOfLine {
 1811                stop_at_soft_wraps: true,
 1812                stop_at_indent: true,
 1813            },
 1814            window,
 1815            cx,
 1816        );
 1817        assert_eq!(
 1818            editor.selections.display_ranges(cx),
 1819            &[
 1820                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1821                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1822            ]
 1823        );
 1824    });
 1825
 1826    _ = editor.update(cx, |editor, window, cx| {
 1827        editor.select_to_beginning_of_line(
 1828            &SelectToBeginningOfLine {
 1829                stop_at_soft_wraps: true,
 1830                stop_at_indent: true,
 1831            },
 1832            window,
 1833            cx,
 1834        );
 1835        assert_eq!(
 1836            editor.selections.display_ranges(cx),
 1837            &[
 1838                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1839                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1840            ]
 1841        );
 1842    });
 1843
 1844    _ = editor.update(cx, |editor, window, cx| {
 1845        editor.select_to_end_of_line(
 1846            &SelectToEndOfLine {
 1847                stop_at_soft_wraps: true,
 1848            },
 1849            window,
 1850            cx,
 1851        );
 1852        assert_eq!(
 1853            editor.selections.display_ranges(cx),
 1854            &[
 1855                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
 1856                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
 1857            ]
 1858        );
 1859    });
 1860
 1861    _ = editor.update(cx, |editor, window, cx| {
 1862        editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
 1863        assert_eq!(editor.display_text(cx), "ab\n  de");
 1864        assert_eq!(
 1865            editor.selections.display_ranges(cx),
 1866            &[
 1867                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 1868                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1869            ]
 1870        );
 1871    });
 1872
 1873    _ = editor.update(cx, |editor, window, cx| {
 1874        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1875        assert_eq!(editor.display_text(cx), "\n");
 1876        assert_eq!(
 1877            editor.selections.display_ranges(cx),
 1878            &[
 1879                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1880                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1881            ]
 1882        );
 1883    });
 1884}
 1885
 1886#[gpui::test]
 1887fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
 1888    init_test(cx, |_| {});
 1889    let move_to_beg = MoveToBeginningOfLine {
 1890        stop_at_soft_wraps: false,
 1891        stop_at_indent: false,
 1892    };
 1893
 1894    let move_to_end = MoveToEndOfLine {
 1895        stop_at_soft_wraps: false,
 1896    };
 1897
 1898    let editor = cx.add_window(|window, cx| {
 1899        let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
 1900        build_editor(buffer, window, cx)
 1901    });
 1902
 1903    _ = editor.update(cx, |editor, window, cx| {
 1904        editor.set_wrap_width(Some(140.0.into()), cx);
 1905
 1906        // We expect the following lines after wrapping
 1907        // ```
 1908        // thequickbrownfox
 1909        // jumpedoverthelazydo
 1910        // gs
 1911        // ```
 1912        // The final `gs` was soft-wrapped onto a new line.
 1913        assert_eq!(
 1914            "thequickbrownfox\njumpedoverthelaz\nydogs",
 1915            editor.display_text(cx),
 1916        );
 1917
 1918        // First, let's assert behavior on the first line, that was not soft-wrapped.
 1919        // Start the cursor at the `k` on the first line
 1920        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1921            s.select_display_ranges([
 1922                DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
 1923            ]);
 1924        });
 1925
 1926        // Moving to the beginning of the line should put us at the beginning of the line.
 1927        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1928        assert_eq!(
 1929            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
 1930            editor.selections.display_ranges(cx)
 1931        );
 1932
 1933        // Moving to the end of the line should put us at the end of the line.
 1934        editor.move_to_end_of_line(&move_to_end, window, cx);
 1935        assert_eq!(
 1936            vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
 1937            editor.selections.display_ranges(cx)
 1938        );
 1939
 1940        // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
 1941        // Start the cursor at the last line (`y` that was wrapped to a new line)
 1942        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1943            s.select_display_ranges([
 1944                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
 1945            ]);
 1946        });
 1947
 1948        // Moving to the beginning of the line should put us at the start of the second line of
 1949        // display text, i.e., the `j`.
 1950        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1951        assert_eq!(
 1952            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1953            editor.selections.display_ranges(cx)
 1954        );
 1955
 1956        // Moving to the beginning of the line again should be a no-op.
 1957        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1958        assert_eq!(
 1959            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1960            editor.selections.display_ranges(cx)
 1961        );
 1962
 1963        // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
 1964        // next display line.
 1965        editor.move_to_end_of_line(&move_to_end, window, cx);
 1966        assert_eq!(
 1967            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1968            editor.selections.display_ranges(cx)
 1969        );
 1970
 1971        // Moving to the end of the line again should be a no-op.
 1972        editor.move_to_end_of_line(&move_to_end, window, cx);
 1973        assert_eq!(
 1974            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1975            editor.selections.display_ranges(cx)
 1976        );
 1977    });
 1978}
 1979
 1980#[gpui::test]
 1981fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
 1982    init_test(cx, |_| {});
 1983
 1984    let move_to_beg = MoveToBeginningOfLine {
 1985        stop_at_soft_wraps: true,
 1986        stop_at_indent: true,
 1987    };
 1988
 1989    let select_to_beg = SelectToBeginningOfLine {
 1990        stop_at_soft_wraps: true,
 1991        stop_at_indent: true,
 1992    };
 1993
 1994    let delete_to_beg = DeleteToBeginningOfLine {
 1995        stop_at_indent: true,
 1996    };
 1997
 1998    let move_to_end = MoveToEndOfLine {
 1999        stop_at_soft_wraps: false,
 2000    };
 2001
 2002    let editor = cx.add_window(|window, cx| {
 2003        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 2004        build_editor(buffer, window, cx)
 2005    });
 2006
 2007    _ = editor.update(cx, |editor, window, cx| {
 2008        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2009            s.select_display_ranges([
 2010                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 2011                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 2012            ]);
 2013        });
 2014
 2015        // Moving to the beginning of the line should put the first cursor at the beginning of the line,
 2016        // and the second cursor at the first non-whitespace character in the line.
 2017        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2018        assert_eq!(
 2019            editor.selections.display_ranges(cx),
 2020            &[
 2021                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 2022                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2023            ]
 2024        );
 2025
 2026        // Moving to the beginning of the line again should be a no-op for the first cursor,
 2027        // and should move the second cursor to the beginning of the line.
 2028        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2029        assert_eq!(
 2030            editor.selections.display_ranges(cx),
 2031            &[
 2032                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 2033                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 2034            ]
 2035        );
 2036
 2037        // Moving to the beginning of the line again should still be a no-op for the first cursor,
 2038        // and should move the second cursor back to the first non-whitespace character in the line.
 2039        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2040        assert_eq!(
 2041            editor.selections.display_ranges(cx),
 2042            &[
 2043                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 2044                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2045            ]
 2046        );
 2047
 2048        // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
 2049        // and to the first non-whitespace character in the line for the second cursor.
 2050        editor.move_to_end_of_line(&move_to_end, window, cx);
 2051        editor.move_left(&MoveLeft, window, cx);
 2052        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 2053        assert_eq!(
 2054            editor.selections.display_ranges(cx),
 2055            &[
 2056                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 2057                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 2058            ]
 2059        );
 2060
 2061        // Selecting to the beginning of the line again should be a no-op for the first cursor,
 2062        // and should select to the beginning of the line for the second cursor.
 2063        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 2064        assert_eq!(
 2065            editor.selections.display_ranges(cx),
 2066            &[
 2067                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 2068                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 2069            ]
 2070        );
 2071
 2072        // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
 2073        // and should delete to the first non-whitespace character in the line for the second cursor.
 2074        editor.move_to_end_of_line(&move_to_end, window, cx);
 2075        editor.move_left(&MoveLeft, window, cx);
 2076        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 2077        assert_eq!(editor.text(cx), "c\n  f");
 2078    });
 2079}
 2080
 2081#[gpui::test]
 2082fn test_beginning_of_line_with_cursor_between_line_start_and_indent(cx: &mut TestAppContext) {
 2083    init_test(cx, |_| {});
 2084
 2085    let move_to_beg = MoveToBeginningOfLine {
 2086        stop_at_soft_wraps: true,
 2087        stop_at_indent: true,
 2088    };
 2089
 2090    let editor = cx.add_window(|window, cx| {
 2091        let buffer = MultiBuffer::build_simple("    hello\nworld", cx);
 2092        build_editor(buffer, window, cx)
 2093    });
 2094
 2095    _ = editor.update(cx, |editor, window, cx| {
 2096        // test cursor between line_start and indent_start
 2097        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2098            s.select_display_ranges([
 2099                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3)
 2100            ]);
 2101        });
 2102
 2103        // cursor should move to line_start
 2104        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2105        assert_eq!(
 2106            editor.selections.display_ranges(cx),
 2107            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 2108        );
 2109
 2110        // cursor should move to indent_start
 2111        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2112        assert_eq!(
 2113            editor.selections.display_ranges(cx),
 2114            &[DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 4)]
 2115        );
 2116
 2117        // cursor should move to back to line_start
 2118        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2119        assert_eq!(
 2120            editor.selections.display_ranges(cx),
 2121            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 2122        );
 2123    });
 2124}
 2125
 2126#[gpui::test]
 2127fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 2128    init_test(cx, |_| {});
 2129
 2130    let editor = cx.add_window(|window, cx| {
 2131        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
 2132        build_editor(buffer, window, cx)
 2133    });
 2134    _ = editor.update(cx, |editor, window, cx| {
 2135        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2136            s.select_display_ranges([
 2137                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
 2138                DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
 2139            ])
 2140        });
 2141        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2142        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 2143
 2144        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2145        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\nˇ  {baz.qux()}", editor, cx);
 2146
 2147        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2148        assert_selection_ranges("use ˇstd::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 2149
 2150        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2151        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 2152
 2153        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2154        assert_selection_ranges("ˇuse std::str::{foo, ˇbar}\n\n  {baz.qux()}", editor, cx);
 2155
 2156        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2157        assert_selection_ranges("useˇ std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 2158
 2159        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2160        assert_selection_ranges("use stdˇ::str::{foo, bar}ˇ\n\n  {baz.qux()}", editor, cx);
 2161
 2162        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2163        assert_selection_ranges("use std::ˇstr::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 2164
 2165        editor.move_right(&MoveRight, window, cx);
 2166        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 2167        assert_selection_ranges(
 2168            "use std::«ˇs»tr::{foo, bar}\n«ˇ\n»  {baz.qux()}",
 2169            editor,
 2170            cx,
 2171        );
 2172
 2173        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 2174        assert_selection_ranges(
 2175            "use std«ˇ::s»tr::{foo, bar«ˇ}\n\n»  {baz.qux()}",
 2176            editor,
 2177            cx,
 2178        );
 2179
 2180        editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
 2181        assert_selection_ranges(
 2182            "use std::«ˇs»tr::{foo, bar}«ˇ\n\n»  {baz.qux()}",
 2183            editor,
 2184            cx,
 2185        );
 2186    });
 2187}
 2188
 2189#[gpui::test]
 2190fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 2191    init_test(cx, |_| {});
 2192
 2193    let editor = cx.add_window(|window, cx| {
 2194        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 2195        build_editor(buffer, window, cx)
 2196    });
 2197
 2198    _ = editor.update(cx, |editor, window, cx| {
 2199        editor.set_wrap_width(Some(140.0.into()), cx);
 2200        assert_eq!(
 2201            editor.display_text(cx),
 2202            "use one::{\n    two::three::\n    four::five\n};"
 2203        );
 2204
 2205        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2206            s.select_display_ranges([
 2207                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
 2208            ]);
 2209        });
 2210
 2211        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2212        assert_eq!(
 2213            editor.selections.display_ranges(cx),
 2214            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
 2215        );
 2216
 2217        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2218        assert_eq!(
 2219            editor.selections.display_ranges(cx),
 2220            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2221        );
 2222
 2223        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2224        assert_eq!(
 2225            editor.selections.display_ranges(cx),
 2226            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2227        );
 2228
 2229        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2230        assert_eq!(
 2231            editor.selections.display_ranges(cx),
 2232            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
 2233        );
 2234
 2235        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2236        assert_eq!(
 2237            editor.selections.display_ranges(cx),
 2238            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2239        );
 2240
 2241        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2242        assert_eq!(
 2243            editor.selections.display_ranges(cx),
 2244            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2245        );
 2246    });
 2247}
 2248
 2249#[gpui::test]
 2250async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
 2251    init_test(cx, |_| {});
 2252    let mut cx = EditorTestContext::new(cx).await;
 2253
 2254    let line_height = cx.editor(|editor, window, _| {
 2255        editor
 2256            .style()
 2257            .unwrap()
 2258            .text
 2259            .line_height_in_pixels(window.rem_size())
 2260    });
 2261    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2262
 2263    cx.set_state(
 2264        &r#"ˇone
 2265        two
 2266
 2267        three
 2268        fourˇ
 2269        five
 2270
 2271        six"#
 2272            .unindent(),
 2273    );
 2274
 2275    cx.update_editor(|editor, window, cx| {
 2276        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2277    });
 2278    cx.assert_editor_state(
 2279        &r#"one
 2280        two
 2281        ˇ
 2282        three
 2283        four
 2284        five
 2285        ˇ
 2286        six"#
 2287            .unindent(),
 2288    );
 2289
 2290    cx.update_editor(|editor, window, cx| {
 2291        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2292    });
 2293    cx.assert_editor_state(
 2294        &r#"one
 2295        two
 2296
 2297        three
 2298        four
 2299        five
 2300        ˇ
 2301        sixˇ"#
 2302            .unindent(),
 2303    );
 2304
 2305    cx.update_editor(|editor, window, cx| {
 2306        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2307    });
 2308    cx.assert_editor_state(
 2309        &r#"one
 2310        two
 2311
 2312        three
 2313        four
 2314        five
 2315
 2316        sixˇ"#
 2317            .unindent(),
 2318    );
 2319
 2320    cx.update_editor(|editor, window, cx| {
 2321        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2322    });
 2323    cx.assert_editor_state(
 2324        &r#"one
 2325        two
 2326
 2327        three
 2328        four
 2329        five
 2330        ˇ
 2331        six"#
 2332            .unindent(),
 2333    );
 2334
 2335    cx.update_editor(|editor, window, cx| {
 2336        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2337    });
 2338    cx.assert_editor_state(
 2339        &r#"one
 2340        two
 2341        ˇ
 2342        three
 2343        four
 2344        five
 2345
 2346        six"#
 2347            .unindent(),
 2348    );
 2349
 2350    cx.update_editor(|editor, window, cx| {
 2351        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2352    });
 2353    cx.assert_editor_state(
 2354        &r#"ˇone
 2355        two
 2356
 2357        three
 2358        four
 2359        five
 2360
 2361        six"#
 2362            .unindent(),
 2363    );
 2364}
 2365
 2366#[gpui::test]
 2367async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2368    init_test(cx, |_| {});
 2369    let mut cx = EditorTestContext::new(cx).await;
 2370    let line_height = cx.editor(|editor, window, _| {
 2371        editor
 2372            .style()
 2373            .unwrap()
 2374            .text
 2375            .line_height_in_pixels(window.rem_size())
 2376    });
 2377    let window = cx.window;
 2378    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2379
 2380    cx.set_state(
 2381        r#"ˇone
 2382        two
 2383        three
 2384        four
 2385        five
 2386        six
 2387        seven
 2388        eight
 2389        nine
 2390        ten
 2391        "#,
 2392    );
 2393
 2394    cx.update_editor(|editor, window, cx| {
 2395        assert_eq!(
 2396            editor.snapshot(window, cx).scroll_position(),
 2397            gpui::Point::new(0., 0.)
 2398        );
 2399        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2400        assert_eq!(
 2401            editor.snapshot(window, cx).scroll_position(),
 2402            gpui::Point::new(0., 3.)
 2403        );
 2404        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2405        assert_eq!(
 2406            editor.snapshot(window, cx).scroll_position(),
 2407            gpui::Point::new(0., 6.)
 2408        );
 2409        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2410        assert_eq!(
 2411            editor.snapshot(window, cx).scroll_position(),
 2412            gpui::Point::new(0., 3.)
 2413        );
 2414
 2415        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2416        assert_eq!(
 2417            editor.snapshot(window, cx).scroll_position(),
 2418            gpui::Point::new(0., 1.)
 2419        );
 2420        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2421        assert_eq!(
 2422            editor.snapshot(window, cx).scroll_position(),
 2423            gpui::Point::new(0., 3.)
 2424        );
 2425    });
 2426}
 2427
 2428#[gpui::test]
 2429async fn test_autoscroll(cx: &mut TestAppContext) {
 2430    init_test(cx, |_| {});
 2431    let mut cx = EditorTestContext::new(cx).await;
 2432
 2433    let line_height = cx.update_editor(|editor, window, cx| {
 2434        editor.set_vertical_scroll_margin(2, cx);
 2435        editor
 2436            .style()
 2437            .unwrap()
 2438            .text
 2439            .line_height_in_pixels(window.rem_size())
 2440    });
 2441    let window = cx.window;
 2442    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2443
 2444    cx.set_state(
 2445        r#"ˇone
 2446            two
 2447            three
 2448            four
 2449            five
 2450            six
 2451            seven
 2452            eight
 2453            nine
 2454            ten
 2455        "#,
 2456    );
 2457    cx.update_editor(|editor, window, cx| {
 2458        assert_eq!(
 2459            editor.snapshot(window, cx).scroll_position(),
 2460            gpui::Point::new(0., 0.0)
 2461        );
 2462    });
 2463
 2464    // Add a cursor below the visible area. Since both cursors cannot fit
 2465    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2466    // allows the vertical scroll margin below that cursor.
 2467    cx.update_editor(|editor, window, cx| {
 2468        editor.change_selections(Default::default(), window, cx, |selections| {
 2469            selections.select_ranges([
 2470                Point::new(0, 0)..Point::new(0, 0),
 2471                Point::new(6, 0)..Point::new(6, 0),
 2472            ]);
 2473        })
 2474    });
 2475    cx.update_editor(|editor, window, cx| {
 2476        assert_eq!(
 2477            editor.snapshot(window, cx).scroll_position(),
 2478            gpui::Point::new(0., 3.0)
 2479        );
 2480    });
 2481
 2482    // Move down. The editor cursor scrolls down to track the newest cursor.
 2483    cx.update_editor(|editor, window, cx| {
 2484        editor.move_down(&Default::default(), window, cx);
 2485    });
 2486    cx.update_editor(|editor, window, cx| {
 2487        assert_eq!(
 2488            editor.snapshot(window, cx).scroll_position(),
 2489            gpui::Point::new(0., 4.0)
 2490        );
 2491    });
 2492
 2493    // Add a cursor above the visible area. Since both cursors fit on screen,
 2494    // the editor scrolls to show both.
 2495    cx.update_editor(|editor, window, cx| {
 2496        editor.change_selections(Default::default(), window, cx, |selections| {
 2497            selections.select_ranges([
 2498                Point::new(1, 0)..Point::new(1, 0),
 2499                Point::new(6, 0)..Point::new(6, 0),
 2500            ]);
 2501        })
 2502    });
 2503    cx.update_editor(|editor, window, cx| {
 2504        assert_eq!(
 2505            editor.snapshot(window, cx).scroll_position(),
 2506            gpui::Point::new(0., 1.0)
 2507        );
 2508    });
 2509}
 2510
 2511#[gpui::test]
 2512async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2513    init_test(cx, |_| {});
 2514    let mut cx = EditorTestContext::new(cx).await;
 2515
 2516    let line_height = cx.editor(|editor, window, _cx| {
 2517        editor
 2518            .style()
 2519            .unwrap()
 2520            .text
 2521            .line_height_in_pixels(window.rem_size())
 2522    });
 2523    let window = cx.window;
 2524    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2525    cx.set_state(
 2526        &r#"
 2527        ˇone
 2528        two
 2529        threeˇ
 2530        four
 2531        five
 2532        six
 2533        seven
 2534        eight
 2535        nine
 2536        ten
 2537        "#
 2538        .unindent(),
 2539    );
 2540
 2541    cx.update_editor(|editor, window, cx| {
 2542        editor.move_page_down(&MovePageDown::default(), window, cx)
 2543    });
 2544    cx.assert_editor_state(
 2545        &r#"
 2546        one
 2547        two
 2548        three
 2549        ˇfour
 2550        five
 2551        sixˇ
 2552        seven
 2553        eight
 2554        nine
 2555        ten
 2556        "#
 2557        .unindent(),
 2558    );
 2559
 2560    cx.update_editor(|editor, window, cx| {
 2561        editor.move_page_down(&MovePageDown::default(), window, cx)
 2562    });
 2563    cx.assert_editor_state(
 2564        &r#"
 2565        one
 2566        two
 2567        three
 2568        four
 2569        five
 2570        six
 2571        ˇseven
 2572        eight
 2573        nineˇ
 2574        ten
 2575        "#
 2576        .unindent(),
 2577    );
 2578
 2579    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2580    cx.assert_editor_state(
 2581        &r#"
 2582        one
 2583        two
 2584        three
 2585        ˇfour
 2586        five
 2587        sixˇ
 2588        seven
 2589        eight
 2590        nine
 2591        ten
 2592        "#
 2593        .unindent(),
 2594    );
 2595
 2596    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2597    cx.assert_editor_state(
 2598        &r#"
 2599        ˇone
 2600        two
 2601        threeˇ
 2602        four
 2603        five
 2604        six
 2605        seven
 2606        eight
 2607        nine
 2608        ten
 2609        "#
 2610        .unindent(),
 2611    );
 2612
 2613    // Test select collapsing
 2614    cx.update_editor(|editor, window, cx| {
 2615        editor.move_page_down(&MovePageDown::default(), window, cx);
 2616        editor.move_page_down(&MovePageDown::default(), window, cx);
 2617        editor.move_page_down(&MovePageDown::default(), window, cx);
 2618    });
 2619    cx.assert_editor_state(
 2620        &r#"
 2621        one
 2622        two
 2623        three
 2624        four
 2625        five
 2626        six
 2627        seven
 2628        eight
 2629        nine
 2630        ˇten
 2631        ˇ"#
 2632        .unindent(),
 2633    );
 2634}
 2635
 2636#[gpui::test]
 2637async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2638    init_test(cx, |_| {});
 2639    let mut cx = EditorTestContext::new(cx).await;
 2640    cx.set_state("one «two threeˇ» four");
 2641    cx.update_editor(|editor, window, cx| {
 2642        editor.delete_to_beginning_of_line(
 2643            &DeleteToBeginningOfLine {
 2644                stop_at_indent: false,
 2645            },
 2646            window,
 2647            cx,
 2648        );
 2649        assert_eq!(editor.text(cx), " four");
 2650    });
 2651}
 2652
 2653#[gpui::test]
 2654async fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2655    init_test(cx, |_| {});
 2656
 2657    let mut cx = EditorTestContext::new(cx).await;
 2658
 2659    // For an empty selection, the preceding word fragment is deleted.
 2660    // For non-empty selections, only selected characters are deleted.
 2661    cx.set_state("onˇe two t«hreˇ»e four");
 2662    cx.update_editor(|editor, window, cx| {
 2663        editor.delete_to_previous_word_start(
 2664            &DeleteToPreviousWordStart {
 2665                ignore_newlines: false,
 2666                ignore_brackets: false,
 2667            },
 2668            window,
 2669            cx,
 2670        );
 2671    });
 2672    cx.assert_editor_state("ˇe two tˇe four");
 2673
 2674    cx.set_state("e tˇwo te «fˇ»our");
 2675    cx.update_editor(|editor, window, cx| {
 2676        editor.delete_to_next_word_end(
 2677            &DeleteToNextWordEnd {
 2678                ignore_newlines: false,
 2679                ignore_brackets: false,
 2680            },
 2681            window,
 2682            cx,
 2683        );
 2684    });
 2685    cx.assert_editor_state("e tˇ te ˇour");
 2686}
 2687
 2688#[gpui::test]
 2689async fn test_delete_whitespaces(cx: &mut TestAppContext) {
 2690    init_test(cx, |_| {});
 2691
 2692    let mut cx = EditorTestContext::new(cx).await;
 2693
 2694    cx.set_state("here is some text    ˇwith a space");
 2695    cx.update_editor(|editor, window, cx| {
 2696        editor.delete_to_previous_word_start(
 2697            &DeleteToPreviousWordStart {
 2698                ignore_newlines: false,
 2699                ignore_brackets: true,
 2700            },
 2701            window,
 2702            cx,
 2703        );
 2704    });
 2705    // Continuous whitespace sequences are removed entirely, words behind them are not affected by the deletion action.
 2706    cx.assert_editor_state("here is some textˇwith a space");
 2707
 2708    cx.set_state("here is some text    ˇwith a space");
 2709    cx.update_editor(|editor, window, cx| {
 2710        editor.delete_to_previous_word_start(
 2711            &DeleteToPreviousWordStart {
 2712                ignore_newlines: false,
 2713                ignore_brackets: false,
 2714            },
 2715            window,
 2716            cx,
 2717        );
 2718    });
 2719    cx.assert_editor_state("here is some textˇwith a space");
 2720
 2721    cx.set_state("here is some textˇ    with a space");
 2722    cx.update_editor(|editor, window, cx| {
 2723        editor.delete_to_next_word_end(
 2724            &DeleteToNextWordEnd {
 2725                ignore_newlines: false,
 2726                ignore_brackets: true,
 2727            },
 2728            window,
 2729            cx,
 2730        );
 2731    });
 2732    // Same happens in the other direction.
 2733    cx.assert_editor_state("here is some textˇwith a space");
 2734
 2735    cx.set_state("here is some textˇ    with a space");
 2736    cx.update_editor(|editor, window, cx| {
 2737        editor.delete_to_next_word_end(
 2738            &DeleteToNextWordEnd {
 2739                ignore_newlines: false,
 2740                ignore_brackets: false,
 2741            },
 2742            window,
 2743            cx,
 2744        );
 2745    });
 2746    cx.assert_editor_state("here is some textˇwith a space");
 2747
 2748    cx.set_state("here is some textˇ    with a space");
 2749    cx.update_editor(|editor, window, cx| {
 2750        editor.delete_to_next_word_end(
 2751            &DeleteToNextWordEnd {
 2752                ignore_newlines: true,
 2753                ignore_brackets: false,
 2754            },
 2755            window,
 2756            cx,
 2757        );
 2758    });
 2759    cx.assert_editor_state("here is some textˇwith a space");
 2760    cx.update_editor(|editor, window, cx| {
 2761        editor.delete_to_previous_word_start(
 2762            &DeleteToPreviousWordStart {
 2763                ignore_newlines: true,
 2764                ignore_brackets: false,
 2765            },
 2766            window,
 2767            cx,
 2768        );
 2769    });
 2770    cx.assert_editor_state("here is some ˇwith a space");
 2771    cx.update_editor(|editor, window, cx| {
 2772        editor.delete_to_previous_word_start(
 2773            &DeleteToPreviousWordStart {
 2774                ignore_newlines: true,
 2775                ignore_brackets: false,
 2776            },
 2777            window,
 2778            cx,
 2779        );
 2780    });
 2781    // Single whitespaces are removed with the word behind them.
 2782    cx.assert_editor_state("here is ˇwith a space");
 2783    cx.update_editor(|editor, window, cx| {
 2784        editor.delete_to_previous_word_start(
 2785            &DeleteToPreviousWordStart {
 2786                ignore_newlines: true,
 2787                ignore_brackets: false,
 2788            },
 2789            window,
 2790            cx,
 2791        );
 2792    });
 2793    cx.assert_editor_state("here ˇwith a space");
 2794    cx.update_editor(|editor, window, cx| {
 2795        editor.delete_to_previous_word_start(
 2796            &DeleteToPreviousWordStart {
 2797                ignore_newlines: true,
 2798                ignore_brackets: false,
 2799            },
 2800            window,
 2801            cx,
 2802        );
 2803    });
 2804    cx.assert_editor_state("ˇwith a space");
 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    cx.assert_editor_state("ˇwith a space");
 2816    cx.update_editor(|editor, window, cx| {
 2817        editor.delete_to_next_word_end(
 2818            &DeleteToNextWordEnd {
 2819                ignore_newlines: true,
 2820                ignore_brackets: false,
 2821            },
 2822            window,
 2823            cx,
 2824        );
 2825    });
 2826    // Same happens in the other direction.
 2827    cx.assert_editor_state("ˇ a space");
 2828    cx.update_editor(|editor, window, cx| {
 2829        editor.delete_to_next_word_end(
 2830            &DeleteToNextWordEnd {
 2831                ignore_newlines: true,
 2832                ignore_brackets: false,
 2833            },
 2834            window,
 2835            cx,
 2836        );
 2837    });
 2838    cx.assert_editor_state("ˇ space");
 2839    cx.update_editor(|editor, window, cx| {
 2840        editor.delete_to_next_word_end(
 2841            &DeleteToNextWordEnd {
 2842                ignore_newlines: true,
 2843                ignore_brackets: false,
 2844            },
 2845            window,
 2846            cx,
 2847        );
 2848    });
 2849    cx.assert_editor_state("ˇ");
 2850    cx.update_editor(|editor, window, cx| {
 2851        editor.delete_to_next_word_end(
 2852            &DeleteToNextWordEnd {
 2853                ignore_newlines: true,
 2854                ignore_brackets: false,
 2855            },
 2856            window,
 2857            cx,
 2858        );
 2859    });
 2860    cx.assert_editor_state("ˇ");
 2861    cx.update_editor(|editor, window, cx| {
 2862        editor.delete_to_previous_word_start(
 2863            &DeleteToPreviousWordStart {
 2864                ignore_newlines: true,
 2865                ignore_brackets: false,
 2866            },
 2867            window,
 2868            cx,
 2869        );
 2870    });
 2871    cx.assert_editor_state("ˇ");
 2872}
 2873
 2874#[gpui::test]
 2875async fn test_delete_to_bracket(cx: &mut TestAppContext) {
 2876    init_test(cx, |_| {});
 2877
 2878    let language = Arc::new(
 2879        Language::new(
 2880            LanguageConfig {
 2881                brackets: BracketPairConfig {
 2882                    pairs: vec![
 2883                        BracketPair {
 2884                            start: "\"".to_string(),
 2885                            end: "\"".to_string(),
 2886                            close: true,
 2887                            surround: true,
 2888                            newline: false,
 2889                        },
 2890                        BracketPair {
 2891                            start: "(".to_string(),
 2892                            end: ")".to_string(),
 2893                            close: true,
 2894                            surround: true,
 2895                            newline: true,
 2896                        },
 2897                    ],
 2898                    ..BracketPairConfig::default()
 2899                },
 2900                ..LanguageConfig::default()
 2901            },
 2902            Some(tree_sitter_rust::LANGUAGE.into()),
 2903        )
 2904        .with_brackets_query(
 2905            r#"
 2906                ("(" @open ")" @close)
 2907                ("\"" @open "\"" @close)
 2908            "#,
 2909        )
 2910        .unwrap(),
 2911    );
 2912
 2913    let mut cx = EditorTestContext::new(cx).await;
 2914    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2915
 2916    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 2917    cx.update_editor(|editor, window, cx| {
 2918        editor.delete_to_previous_word_start(
 2919            &DeleteToPreviousWordStart {
 2920                ignore_newlines: true,
 2921                ignore_brackets: false,
 2922            },
 2923            window,
 2924            cx,
 2925        );
 2926    });
 2927    // Deletion stops before brackets if asked to not ignore them.
 2928    cx.assert_editor_state(r#"macro!("ˇCOMMENT");"#);
 2929    cx.update_editor(|editor, window, cx| {
 2930        editor.delete_to_previous_word_start(
 2931            &DeleteToPreviousWordStart {
 2932                ignore_newlines: true,
 2933                ignore_brackets: false,
 2934            },
 2935            window,
 2936            cx,
 2937        );
 2938    });
 2939    // Deletion has to remove a single bracket and then stop again.
 2940    cx.assert_editor_state(r#"macro!(ˇCOMMENT");"#);
 2941
 2942    cx.update_editor(|editor, window, cx| {
 2943        editor.delete_to_previous_word_start(
 2944            &DeleteToPreviousWordStart {
 2945                ignore_newlines: true,
 2946                ignore_brackets: false,
 2947            },
 2948            window,
 2949            cx,
 2950        );
 2951    });
 2952    cx.assert_editor_state(r#"macro!ˇCOMMENT");"#);
 2953
 2954    cx.update_editor(|editor, window, cx| {
 2955        editor.delete_to_previous_word_start(
 2956            &DeleteToPreviousWordStart {
 2957                ignore_newlines: true,
 2958                ignore_brackets: false,
 2959            },
 2960            window,
 2961            cx,
 2962        );
 2963    });
 2964    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2965
 2966    cx.update_editor(|editor, window, cx| {
 2967        editor.delete_to_previous_word_start(
 2968            &DeleteToPreviousWordStart {
 2969                ignore_newlines: true,
 2970                ignore_brackets: false,
 2971            },
 2972            window,
 2973            cx,
 2974        );
 2975    });
 2976    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2977
 2978    cx.update_editor(|editor, window, cx| {
 2979        editor.delete_to_next_word_end(
 2980            &DeleteToNextWordEnd {
 2981                ignore_newlines: true,
 2982                ignore_brackets: false,
 2983            },
 2984            window,
 2985            cx,
 2986        );
 2987    });
 2988    // Brackets on the right are not paired anymore, hence deletion does not stop at them
 2989    cx.assert_editor_state(r#"ˇ");"#);
 2990
 2991    cx.update_editor(|editor, window, cx| {
 2992        editor.delete_to_next_word_end(
 2993            &DeleteToNextWordEnd {
 2994                ignore_newlines: true,
 2995                ignore_brackets: false,
 2996            },
 2997            window,
 2998            cx,
 2999        );
 3000    });
 3001    cx.assert_editor_state(r#"ˇ"#);
 3002
 3003    cx.update_editor(|editor, window, cx| {
 3004        editor.delete_to_next_word_end(
 3005            &DeleteToNextWordEnd {
 3006                ignore_newlines: true,
 3007                ignore_brackets: false,
 3008            },
 3009            window,
 3010            cx,
 3011        );
 3012    });
 3013    cx.assert_editor_state(r#"ˇ"#);
 3014
 3015    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 3016    cx.update_editor(|editor, window, cx| {
 3017        editor.delete_to_previous_word_start(
 3018            &DeleteToPreviousWordStart {
 3019                ignore_newlines: true,
 3020                ignore_brackets: true,
 3021            },
 3022            window,
 3023            cx,
 3024        );
 3025    });
 3026    cx.assert_editor_state(r#"macroˇCOMMENT");"#);
 3027}
 3028
 3029#[gpui::test]
 3030fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 3031    init_test(cx, |_| {});
 3032
 3033    let editor = cx.add_window(|window, cx| {
 3034        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 3035        build_editor(buffer, window, cx)
 3036    });
 3037    let del_to_prev_word_start = DeleteToPreviousWordStart {
 3038        ignore_newlines: false,
 3039        ignore_brackets: false,
 3040    };
 3041    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 3042        ignore_newlines: true,
 3043        ignore_brackets: false,
 3044    };
 3045
 3046    _ = editor.update(cx, |editor, window, cx| {
 3047        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3048            s.select_display_ranges([
 3049                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 3050            ])
 3051        });
 3052        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3053        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 3054        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3055        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
 3056        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3057        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
 3058        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3059        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
 3060        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 3061        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 3062        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 3063        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3064    });
 3065}
 3066
 3067#[gpui::test]
 3068fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 3069    init_test(cx, |_| {});
 3070
 3071    let editor = cx.add_window(|window, cx| {
 3072        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 3073        build_editor(buffer, window, cx)
 3074    });
 3075    let del_to_next_word_end = DeleteToNextWordEnd {
 3076        ignore_newlines: false,
 3077        ignore_brackets: false,
 3078    };
 3079    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 3080        ignore_newlines: true,
 3081        ignore_brackets: false,
 3082    };
 3083
 3084    _ = editor.update(cx, |editor, window, cx| {
 3085        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3086            s.select_display_ranges([
 3087                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 3088            ])
 3089        });
 3090        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3091        assert_eq!(
 3092            editor.buffer.read(cx).read(cx).text(),
 3093            "one\n   two\nthree\n   four"
 3094        );
 3095        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3096        assert_eq!(
 3097            editor.buffer.read(cx).read(cx).text(),
 3098            "\n   two\nthree\n   four"
 3099        );
 3100        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3101        assert_eq!(
 3102            editor.buffer.read(cx).read(cx).text(),
 3103            "two\nthree\n   four"
 3104        );
 3105        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3106        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 3107        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3108        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   four");
 3109        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3110        assert_eq!(editor.buffer.read(cx).read(cx).text(), "four");
 3111        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3112        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3113    });
 3114}
 3115
 3116#[gpui::test]
 3117fn test_newline(cx: &mut TestAppContext) {
 3118    init_test(cx, |_| {});
 3119
 3120    let editor = cx.add_window(|window, cx| {
 3121        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 3122        build_editor(buffer, window, cx)
 3123    });
 3124
 3125    _ = editor.update(cx, |editor, window, cx| {
 3126        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3127            s.select_display_ranges([
 3128                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 3129                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 3130                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 3131            ])
 3132        });
 3133
 3134        editor.newline(&Newline, window, cx);
 3135        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 3136    });
 3137}
 3138
 3139#[gpui::test]
 3140async fn test_newline_yaml(cx: &mut TestAppContext) {
 3141    init_test(cx, |_| {});
 3142
 3143    let mut cx = EditorTestContext::new(cx).await;
 3144    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3145    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3146
 3147    // Object (between 2 fields)
 3148    cx.set_state(indoc! {"
 3149    test:ˇ
 3150    hello: bye"});
 3151    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3152    cx.assert_editor_state(indoc! {"
 3153    test:
 3154        ˇ
 3155    hello: bye"});
 3156
 3157    // Object (first and single line)
 3158    cx.set_state(indoc! {"
 3159    test:ˇ"});
 3160    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3161    cx.assert_editor_state(indoc! {"
 3162    test:
 3163        ˇ"});
 3164
 3165    // Array with objects (after first element)
 3166    cx.set_state(indoc! {"
 3167    test:
 3168        - foo: barˇ"});
 3169    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3170    cx.assert_editor_state(indoc! {"
 3171    test:
 3172        - foo: bar
 3173        ˇ"});
 3174
 3175    // Array with objects and comment
 3176    cx.set_state(indoc! {"
 3177    test:
 3178        - foo: bar
 3179        - bar: # testˇ"});
 3180    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3181    cx.assert_editor_state(indoc! {"
 3182    test:
 3183        - foo: bar
 3184        - bar: # test
 3185            ˇ"});
 3186
 3187    // Array with objects (after second element)
 3188    cx.set_state(indoc! {"
 3189    test:
 3190        - foo: bar
 3191        - bar: fooˇ"});
 3192    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3193    cx.assert_editor_state(indoc! {"
 3194    test:
 3195        - foo: bar
 3196        - bar: foo
 3197        ˇ"});
 3198
 3199    // Array with strings (after first element)
 3200    cx.set_state(indoc! {"
 3201    test:
 3202        - fooˇ"});
 3203    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3204    cx.assert_editor_state(indoc! {"
 3205    test:
 3206        - foo
 3207        ˇ"});
 3208}
 3209
 3210#[gpui::test]
 3211fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 3212    init_test(cx, |_| {});
 3213
 3214    let editor = cx.add_window(|window, cx| {
 3215        let buffer = MultiBuffer::build_simple(
 3216            "
 3217                a
 3218                b(
 3219                    X
 3220                )
 3221                c(
 3222                    X
 3223                )
 3224            "
 3225            .unindent()
 3226            .as_str(),
 3227            cx,
 3228        );
 3229        let mut editor = build_editor(buffer, window, cx);
 3230        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3231            s.select_ranges([
 3232                Point::new(2, 4)..Point::new(2, 5),
 3233                Point::new(5, 4)..Point::new(5, 5),
 3234            ])
 3235        });
 3236        editor
 3237    });
 3238
 3239    _ = editor.update(cx, |editor, window, cx| {
 3240        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3241        editor.buffer.update(cx, |buffer, cx| {
 3242            buffer.edit(
 3243                [
 3244                    (Point::new(1, 2)..Point::new(3, 0), ""),
 3245                    (Point::new(4, 2)..Point::new(6, 0), ""),
 3246                ],
 3247                None,
 3248                cx,
 3249            );
 3250            assert_eq!(
 3251                buffer.read(cx).text(),
 3252                "
 3253                    a
 3254                    b()
 3255                    c()
 3256                "
 3257                .unindent()
 3258            );
 3259        });
 3260        assert_eq!(
 3261            editor.selections.ranges(&editor.display_snapshot(cx)),
 3262            &[
 3263                Point::new(1, 2)..Point::new(1, 2),
 3264                Point::new(2, 2)..Point::new(2, 2),
 3265            ],
 3266        );
 3267
 3268        editor.newline(&Newline, window, cx);
 3269        assert_eq!(
 3270            editor.text(cx),
 3271            "
 3272                a
 3273                b(
 3274                )
 3275                c(
 3276                )
 3277            "
 3278            .unindent()
 3279        );
 3280
 3281        // The selections are moved after the inserted newlines
 3282        assert_eq!(
 3283            editor.selections.ranges(&editor.display_snapshot(cx)),
 3284            &[
 3285                Point::new(2, 0)..Point::new(2, 0),
 3286                Point::new(4, 0)..Point::new(4, 0),
 3287            ],
 3288        );
 3289    });
 3290}
 3291
 3292#[gpui::test]
 3293async fn test_newline_above(cx: &mut TestAppContext) {
 3294    init_test(cx, |settings| {
 3295        settings.defaults.tab_size = NonZeroU32::new(4)
 3296    });
 3297
 3298    let language = Arc::new(
 3299        Language::new(
 3300            LanguageConfig::default(),
 3301            Some(tree_sitter_rust::LANGUAGE.into()),
 3302        )
 3303        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3304        .unwrap(),
 3305    );
 3306
 3307    let mut cx = EditorTestContext::new(cx).await;
 3308    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3309    cx.set_state(indoc! {"
 3310        const a: ˇA = (
 3311 3312                «const_functionˇ»(ˇ),
 3313                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3314 3315        ˇ);ˇ
 3316    "});
 3317
 3318    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 3319    cx.assert_editor_state(indoc! {"
 3320        ˇ
 3321        const a: A = (
 3322            ˇ
 3323            (
 3324                ˇ
 3325                ˇ
 3326                const_function(),
 3327                ˇ
 3328                ˇ
 3329                ˇ
 3330                ˇ
 3331                something_else,
 3332                ˇ
 3333            )
 3334            ˇ
 3335            ˇ
 3336        );
 3337    "});
 3338}
 3339
 3340#[gpui::test]
 3341async fn test_newline_below(cx: &mut TestAppContext) {
 3342    init_test(cx, |settings| {
 3343        settings.defaults.tab_size = NonZeroU32::new(4)
 3344    });
 3345
 3346    let language = Arc::new(
 3347        Language::new(
 3348            LanguageConfig::default(),
 3349            Some(tree_sitter_rust::LANGUAGE.into()),
 3350        )
 3351        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3352        .unwrap(),
 3353    );
 3354
 3355    let mut cx = EditorTestContext::new(cx).await;
 3356    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3357    cx.set_state(indoc! {"
 3358        const a: ˇA = (
 3359 3360                «const_functionˇ»(ˇ),
 3361                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3362 3363        ˇ);ˇ
 3364    "});
 3365
 3366    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 3367    cx.assert_editor_state(indoc! {"
 3368        const a: A = (
 3369            ˇ
 3370            (
 3371                ˇ
 3372                const_function(),
 3373                ˇ
 3374                ˇ
 3375                something_else,
 3376                ˇ
 3377                ˇ
 3378                ˇ
 3379                ˇ
 3380            )
 3381            ˇ
 3382        );
 3383        ˇ
 3384        ˇ
 3385    "});
 3386}
 3387
 3388#[gpui::test]
 3389async fn test_newline_comments(cx: &mut TestAppContext) {
 3390    init_test(cx, |settings| {
 3391        settings.defaults.tab_size = NonZeroU32::new(4)
 3392    });
 3393
 3394    let language = Arc::new(Language::new(
 3395        LanguageConfig {
 3396            line_comments: vec!["// ".into()],
 3397            ..LanguageConfig::default()
 3398        },
 3399        None,
 3400    ));
 3401    {
 3402        let mut cx = EditorTestContext::new(cx).await;
 3403        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3404        cx.set_state(indoc! {"
 3405        // Fooˇ
 3406    "});
 3407
 3408        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3409        cx.assert_editor_state(indoc! {"
 3410        // Foo
 3411        // ˇ
 3412    "});
 3413        // Ensure that we add comment prefix when existing line contains space
 3414        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3415        cx.assert_editor_state(
 3416            indoc! {"
 3417        // Foo
 3418        //s
 3419        // ˇ
 3420    "}
 3421            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3422            .as_str(),
 3423        );
 3424        // Ensure that we add comment prefix when existing line does not contain space
 3425        cx.set_state(indoc! {"
 3426        // Foo
 3427        //ˇ
 3428    "});
 3429        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3430        cx.assert_editor_state(indoc! {"
 3431        // Foo
 3432        //
 3433        // ˇ
 3434    "});
 3435        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 3436        cx.set_state(indoc! {"
 3437        ˇ// Foo
 3438    "});
 3439        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3440        cx.assert_editor_state(indoc! {"
 3441
 3442        ˇ// Foo
 3443    "});
 3444    }
 3445    // Ensure that comment continuations can be disabled.
 3446    update_test_language_settings(cx, |settings| {
 3447        settings.defaults.extend_comment_on_newline = Some(false);
 3448    });
 3449    let mut cx = EditorTestContext::new(cx).await;
 3450    cx.set_state(indoc! {"
 3451        // Fooˇ
 3452    "});
 3453    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3454    cx.assert_editor_state(indoc! {"
 3455        // Foo
 3456        ˇ
 3457    "});
 3458}
 3459
 3460#[gpui::test]
 3461async fn test_newline_comments_with_multiple_delimiters(cx: &mut TestAppContext) {
 3462    init_test(cx, |settings| {
 3463        settings.defaults.tab_size = NonZeroU32::new(4)
 3464    });
 3465
 3466    let language = Arc::new(Language::new(
 3467        LanguageConfig {
 3468            line_comments: vec!["// ".into(), "/// ".into()],
 3469            ..LanguageConfig::default()
 3470        },
 3471        None,
 3472    ));
 3473    {
 3474        let mut cx = EditorTestContext::new(cx).await;
 3475        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3476        cx.set_state(indoc! {"
 3477        //ˇ
 3478    "});
 3479        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3480        cx.assert_editor_state(indoc! {"
 3481        //
 3482        // ˇ
 3483    "});
 3484
 3485        cx.set_state(indoc! {"
 3486        ///ˇ
 3487    "});
 3488        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3489        cx.assert_editor_state(indoc! {"
 3490        ///
 3491        /// ˇ
 3492    "});
 3493    }
 3494}
 3495
 3496#[gpui::test]
 3497async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 3498    init_test(cx, |settings| {
 3499        settings.defaults.tab_size = NonZeroU32::new(4)
 3500    });
 3501
 3502    let language = Arc::new(
 3503        Language::new(
 3504            LanguageConfig {
 3505                documentation_comment: Some(language::BlockCommentConfig {
 3506                    start: "/**".into(),
 3507                    end: "*/".into(),
 3508                    prefix: "* ".into(),
 3509                    tab_size: 1,
 3510                }),
 3511
 3512                ..LanguageConfig::default()
 3513            },
 3514            Some(tree_sitter_rust::LANGUAGE.into()),
 3515        )
 3516        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 3517        .unwrap(),
 3518    );
 3519
 3520    {
 3521        let mut cx = EditorTestContext::new(cx).await;
 3522        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3523        cx.set_state(indoc! {"
 3524        /**ˇ
 3525    "});
 3526
 3527        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3528        cx.assert_editor_state(indoc! {"
 3529        /**
 3530         * ˇ
 3531    "});
 3532        // Ensure that if cursor is before the comment start,
 3533        // we do not actually insert a comment prefix.
 3534        cx.set_state(indoc! {"
 3535        ˇ/**
 3536    "});
 3537        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3538        cx.assert_editor_state(indoc! {"
 3539
 3540        ˇ/**
 3541    "});
 3542        // Ensure that if cursor is between it doesn't add comment prefix.
 3543        cx.set_state(indoc! {"
 3544        /*ˇ*
 3545    "});
 3546        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3547        cx.assert_editor_state(indoc! {"
 3548        /*
 3549        ˇ*
 3550    "});
 3551        // Ensure that if suffix exists on same line after cursor it adds new line.
 3552        cx.set_state(indoc! {"
 3553        /**ˇ*/
 3554    "});
 3555        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3556        cx.assert_editor_state(indoc! {"
 3557        /**
 3558         * ˇ
 3559         */
 3560    "});
 3561        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3562        cx.set_state(indoc! {"
 3563        /**ˇ */
 3564    "});
 3565        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3566        cx.assert_editor_state(indoc! {"
 3567        /**
 3568         * ˇ
 3569         */
 3570    "});
 3571        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3572        cx.set_state(indoc! {"
 3573        /** ˇ*/
 3574    "});
 3575        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3576        cx.assert_editor_state(
 3577            indoc! {"
 3578        /**s
 3579         * ˇ
 3580         */
 3581    "}
 3582            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3583            .as_str(),
 3584        );
 3585        // Ensure that delimiter space is preserved when newline on already
 3586        // spaced delimiter.
 3587        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3588        cx.assert_editor_state(
 3589            indoc! {"
 3590        /**s
 3591         *s
 3592         * ˇ
 3593         */
 3594    "}
 3595            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3596            .as_str(),
 3597        );
 3598        // Ensure that delimiter space is preserved when space is not
 3599        // on existing delimiter.
 3600        cx.set_state(indoc! {"
 3601        /**
 3602 3603         */
 3604    "});
 3605        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3606        cx.assert_editor_state(indoc! {"
 3607        /**
 3608         *
 3609         * ˇ
 3610         */
 3611    "});
 3612        // Ensure that if suffix exists on same line after cursor it
 3613        // doesn't add extra new line if prefix is not on same line.
 3614        cx.set_state(indoc! {"
 3615        /**
 3616        ˇ*/
 3617    "});
 3618        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3619        cx.assert_editor_state(indoc! {"
 3620        /**
 3621
 3622        ˇ*/
 3623    "});
 3624        // Ensure that it detects suffix after existing prefix.
 3625        cx.set_state(indoc! {"
 3626        /**ˇ/
 3627    "});
 3628        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3629        cx.assert_editor_state(indoc! {"
 3630        /**
 3631        ˇ/
 3632    "});
 3633        // Ensure that if suffix exists on same line before
 3634        // cursor it does not add comment prefix.
 3635        cx.set_state(indoc! {"
 3636        /** */ˇ
 3637    "});
 3638        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3639        cx.assert_editor_state(indoc! {"
 3640        /** */
 3641        ˇ
 3642    "});
 3643        // Ensure that if suffix exists on same line before
 3644        // cursor it does not add comment prefix.
 3645        cx.set_state(indoc! {"
 3646        /**
 3647         *
 3648         */ˇ
 3649    "});
 3650        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3651        cx.assert_editor_state(indoc! {"
 3652        /**
 3653         *
 3654         */
 3655         ˇ
 3656    "});
 3657
 3658        // Ensure that inline comment followed by code
 3659        // doesn't add comment prefix on newline
 3660        cx.set_state(indoc! {"
 3661        /** */ textˇ
 3662    "});
 3663        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3664        cx.assert_editor_state(indoc! {"
 3665        /** */ text
 3666        ˇ
 3667    "});
 3668
 3669        // Ensure that text after comment end tag
 3670        // doesn't add comment prefix on newline
 3671        cx.set_state(indoc! {"
 3672        /**
 3673         *
 3674         */ˇtext
 3675    "});
 3676        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3677        cx.assert_editor_state(indoc! {"
 3678        /**
 3679         *
 3680         */
 3681         ˇtext
 3682    "});
 3683
 3684        // Ensure if not comment block it doesn't
 3685        // add comment prefix on newline
 3686        cx.set_state(indoc! {"
 3687        * textˇ
 3688    "});
 3689        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3690        cx.assert_editor_state(indoc! {"
 3691        * text
 3692        ˇ
 3693    "});
 3694    }
 3695    // Ensure that comment continuations can be disabled.
 3696    update_test_language_settings(cx, |settings| {
 3697        settings.defaults.extend_comment_on_newline = Some(false);
 3698    });
 3699    let mut cx = EditorTestContext::new(cx).await;
 3700    cx.set_state(indoc! {"
 3701        /**ˇ
 3702    "});
 3703    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3704    cx.assert_editor_state(indoc! {"
 3705        /**
 3706        ˇ
 3707    "});
 3708}
 3709
 3710#[gpui::test]
 3711async fn test_newline_comments_with_block_comment(cx: &mut TestAppContext) {
 3712    init_test(cx, |settings| {
 3713        settings.defaults.tab_size = NonZeroU32::new(4)
 3714    });
 3715
 3716    let lua_language = Arc::new(Language::new(
 3717        LanguageConfig {
 3718            line_comments: vec!["--".into()],
 3719            block_comment: Some(language::BlockCommentConfig {
 3720                start: "--[[".into(),
 3721                prefix: "".into(),
 3722                end: "]]".into(),
 3723                tab_size: 0,
 3724            }),
 3725            ..LanguageConfig::default()
 3726        },
 3727        None,
 3728    ));
 3729
 3730    let mut cx = EditorTestContext::new(cx).await;
 3731    cx.update_buffer(|buffer, cx| buffer.set_language(Some(lua_language), cx));
 3732
 3733    // Line with line comment should extend
 3734    cx.set_state(indoc! {"
 3735        --ˇ
 3736    "});
 3737    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3738    cx.assert_editor_state(indoc! {"
 3739        --
 3740        --ˇ
 3741    "});
 3742
 3743    // Line with block comment that matches line comment should not extend
 3744    cx.set_state(indoc! {"
 3745        --[[ˇ
 3746    "});
 3747    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3748    cx.assert_editor_state(indoc! {"
 3749        --[[
 3750        ˇ
 3751    "});
 3752}
 3753
 3754#[gpui::test]
 3755fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 3756    init_test(cx, |_| {});
 3757
 3758    let editor = cx.add_window(|window, cx| {
 3759        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 3760        let mut editor = build_editor(buffer, window, cx);
 3761        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3762            s.select_ranges([3..4, 11..12, 19..20])
 3763        });
 3764        editor
 3765    });
 3766
 3767    _ = editor.update(cx, |editor, window, cx| {
 3768        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3769        editor.buffer.update(cx, |buffer, cx| {
 3770            buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
 3771            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 3772        });
 3773        assert_eq!(
 3774            editor.selections.ranges(&editor.display_snapshot(cx)),
 3775            &[2..2, 7..7, 12..12],
 3776        );
 3777
 3778        editor.insert("Z", window, cx);
 3779        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 3780
 3781        // The selections are moved after the inserted characters
 3782        assert_eq!(
 3783            editor.selections.ranges(&editor.display_snapshot(cx)),
 3784            &[3..3, 9..9, 15..15],
 3785        );
 3786    });
 3787}
 3788
 3789#[gpui::test]
 3790async fn test_tab(cx: &mut TestAppContext) {
 3791    init_test(cx, |settings| {
 3792        settings.defaults.tab_size = NonZeroU32::new(3)
 3793    });
 3794
 3795    let mut cx = EditorTestContext::new(cx).await;
 3796    cx.set_state(indoc! {"
 3797        ˇabˇc
 3798        ˇ🏀ˇ🏀ˇefg
 3799 3800    "});
 3801    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3802    cx.assert_editor_state(indoc! {"
 3803           ˇab ˇc
 3804           ˇ🏀  ˇ🏀  ˇefg
 3805        d  ˇ
 3806    "});
 3807
 3808    cx.set_state(indoc! {"
 3809        a
 3810        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3811    "});
 3812    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3813    cx.assert_editor_state(indoc! {"
 3814        a
 3815           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3816    "});
 3817}
 3818
 3819#[gpui::test]
 3820async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3821    init_test(cx, |_| {});
 3822
 3823    let mut cx = EditorTestContext::new(cx).await;
 3824    let language = Arc::new(
 3825        Language::new(
 3826            LanguageConfig::default(),
 3827            Some(tree_sitter_rust::LANGUAGE.into()),
 3828        )
 3829        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3830        .unwrap(),
 3831    );
 3832    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3833
 3834    // test when all cursors are not at suggested indent
 3835    // then simply move to their suggested indent location
 3836    cx.set_state(indoc! {"
 3837        const a: B = (
 3838            c(
 3839        ˇ
 3840        ˇ    )
 3841        );
 3842    "});
 3843    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3844    cx.assert_editor_state(indoc! {"
 3845        const a: B = (
 3846            c(
 3847                ˇ
 3848            ˇ)
 3849        );
 3850    "});
 3851
 3852    // test cursor already at suggested indent not moving when
 3853    // other cursors are yet to reach their suggested indents
 3854    cx.set_state(indoc! {"
 3855        ˇ
 3856        const a: B = (
 3857            c(
 3858                d(
 3859        ˇ
 3860                )
 3861        ˇ
 3862        ˇ    )
 3863        );
 3864    "});
 3865    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3866    cx.assert_editor_state(indoc! {"
 3867        ˇ
 3868        const a: B = (
 3869            c(
 3870                d(
 3871                    ˇ
 3872                )
 3873                ˇ
 3874            ˇ)
 3875        );
 3876    "});
 3877    // test when all cursors are at suggested indent then tab is inserted
 3878    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3879    cx.assert_editor_state(indoc! {"
 3880            ˇ
 3881        const a: B = (
 3882            c(
 3883                d(
 3884                        ˇ
 3885                )
 3886                    ˇ
 3887                ˇ)
 3888        );
 3889    "});
 3890
 3891    // test when current indent is less than suggested indent,
 3892    // we adjust line to match suggested indent and move cursor to it
 3893    //
 3894    // when no other cursor is at word boundary, all of them should move
 3895    cx.set_state(indoc! {"
 3896        const a: B = (
 3897            c(
 3898                d(
 3899        ˇ
 3900        ˇ   )
 3901        ˇ   )
 3902        );
 3903    "});
 3904    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3905    cx.assert_editor_state(indoc! {"
 3906        const a: B = (
 3907            c(
 3908                d(
 3909                    ˇ
 3910                ˇ)
 3911            ˇ)
 3912        );
 3913    "});
 3914
 3915    // test when current indent is less than suggested indent,
 3916    // we adjust line to match suggested indent and move cursor to it
 3917    //
 3918    // when some other cursor is at word boundary, it should not move
 3919    cx.set_state(indoc! {"
 3920        const a: B = (
 3921            c(
 3922                d(
 3923        ˇ
 3924        ˇ   )
 3925           ˇ)
 3926        );
 3927    "});
 3928    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3929    cx.assert_editor_state(indoc! {"
 3930        const a: B = (
 3931            c(
 3932                d(
 3933                    ˇ
 3934                ˇ)
 3935            ˇ)
 3936        );
 3937    "});
 3938
 3939    // test when current indent is more than suggested indent,
 3940    // we just move cursor to current indent instead of suggested indent
 3941    //
 3942    // when no other cursor is at word boundary, all of them should move
 3943    cx.set_state(indoc! {"
 3944        const a: B = (
 3945            c(
 3946                d(
 3947        ˇ
 3948        ˇ                )
 3949        ˇ   )
 3950        );
 3951    "});
 3952    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3953    cx.assert_editor_state(indoc! {"
 3954        const a: B = (
 3955            c(
 3956                d(
 3957                    ˇ
 3958                        ˇ)
 3959            ˇ)
 3960        );
 3961    "});
 3962    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3963    cx.assert_editor_state(indoc! {"
 3964        const a: B = (
 3965            c(
 3966                d(
 3967                        ˇ
 3968                            ˇ)
 3969                ˇ)
 3970        );
 3971    "});
 3972
 3973    // test when current indent is more than suggested indent,
 3974    // we just move cursor to current indent instead of suggested indent
 3975    //
 3976    // when some other cursor is at word boundary, it doesn't move
 3977    cx.set_state(indoc! {"
 3978        const a: B = (
 3979            c(
 3980                d(
 3981        ˇ
 3982        ˇ                )
 3983            ˇ)
 3984        );
 3985    "});
 3986    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3987    cx.assert_editor_state(indoc! {"
 3988        const a: B = (
 3989            c(
 3990                d(
 3991                    ˇ
 3992                        ˇ)
 3993            ˇ)
 3994        );
 3995    "});
 3996
 3997    // handle auto-indent when there are multiple cursors on the same line
 3998    cx.set_state(indoc! {"
 3999        const a: B = (
 4000            c(
 4001        ˇ    ˇ
 4002        ˇ    )
 4003        );
 4004    "});
 4005    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4006    cx.assert_editor_state(indoc! {"
 4007        const a: B = (
 4008            c(
 4009                ˇ
 4010            ˇ)
 4011        );
 4012    "});
 4013}
 4014
 4015#[gpui::test]
 4016async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 4017    init_test(cx, |settings| {
 4018        settings.defaults.tab_size = NonZeroU32::new(3)
 4019    });
 4020
 4021    let mut cx = EditorTestContext::new(cx).await;
 4022    cx.set_state(indoc! {"
 4023         ˇ
 4024        \t ˇ
 4025        \t  ˇ
 4026        \t   ˇ
 4027         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 4028    "});
 4029
 4030    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4031    cx.assert_editor_state(indoc! {"
 4032           ˇ
 4033        \t   ˇ
 4034        \t   ˇ
 4035        \t      ˇ
 4036         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 4037    "});
 4038}
 4039
 4040#[gpui::test]
 4041async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 4042    init_test(cx, |settings| {
 4043        settings.defaults.tab_size = NonZeroU32::new(4)
 4044    });
 4045
 4046    let language = Arc::new(
 4047        Language::new(
 4048            LanguageConfig::default(),
 4049            Some(tree_sitter_rust::LANGUAGE.into()),
 4050        )
 4051        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 4052        .unwrap(),
 4053    );
 4054
 4055    let mut cx = EditorTestContext::new(cx).await;
 4056    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 4057    cx.set_state(indoc! {"
 4058        fn a() {
 4059            if b {
 4060        \t ˇc
 4061            }
 4062        }
 4063    "});
 4064
 4065    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4066    cx.assert_editor_state(indoc! {"
 4067        fn a() {
 4068            if b {
 4069                ˇc
 4070            }
 4071        }
 4072    "});
 4073}
 4074
 4075#[gpui::test]
 4076async fn test_indent_outdent(cx: &mut TestAppContext) {
 4077    init_test(cx, |settings| {
 4078        settings.defaults.tab_size = NonZeroU32::new(4);
 4079    });
 4080
 4081    let mut cx = EditorTestContext::new(cx).await;
 4082
 4083    cx.set_state(indoc! {"
 4084          «oneˇ» «twoˇ»
 4085        three
 4086         four
 4087    "});
 4088    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4089    cx.assert_editor_state(indoc! {"
 4090            «oneˇ» «twoˇ»
 4091        three
 4092         four
 4093    "});
 4094
 4095    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4096    cx.assert_editor_state(indoc! {"
 4097        «oneˇ» «twoˇ»
 4098        three
 4099         four
 4100    "});
 4101
 4102    // select across line ending
 4103    cx.set_state(indoc! {"
 4104        one two
 4105        t«hree
 4106        ˇ» four
 4107    "});
 4108    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4109    cx.assert_editor_state(indoc! {"
 4110        one two
 4111            t«hree
 4112        ˇ» four
 4113    "});
 4114
 4115    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4116    cx.assert_editor_state(indoc! {"
 4117        one two
 4118        t«hree
 4119        ˇ» four
 4120    "});
 4121
 4122    // Ensure that indenting/outdenting works when the cursor is at column 0.
 4123    cx.set_state(indoc! {"
 4124        one two
 4125        ˇthree
 4126            four
 4127    "});
 4128    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4129    cx.assert_editor_state(indoc! {"
 4130        one two
 4131            ˇthree
 4132            four
 4133    "});
 4134
 4135    cx.set_state(indoc! {"
 4136        one two
 4137        ˇ    three
 4138            four
 4139    "});
 4140    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4141    cx.assert_editor_state(indoc! {"
 4142        one two
 4143        ˇthree
 4144            four
 4145    "});
 4146}
 4147
 4148#[gpui::test]
 4149async fn test_indent_yaml_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 4150    // This is a regression test for issue #33761
 4151    init_test(cx, |_| {});
 4152
 4153    let mut cx = EditorTestContext::new(cx).await;
 4154    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 4155    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 4156
 4157    cx.set_state(
 4158        r#"ˇ#     ingress:
 4159ˇ#         api:
 4160ˇ#             enabled: false
 4161ˇ#             pathType: Prefix
 4162ˇ#           console:
 4163ˇ#               enabled: false
 4164ˇ#               pathType: Prefix
 4165"#,
 4166    );
 4167
 4168    // Press tab to indent all lines
 4169    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4170
 4171    cx.assert_editor_state(
 4172        r#"    ˇ#     ingress:
 4173    ˇ#         api:
 4174    ˇ#             enabled: false
 4175    ˇ#             pathType: Prefix
 4176    ˇ#           console:
 4177    ˇ#               enabled: false
 4178    ˇ#               pathType: Prefix
 4179"#,
 4180    );
 4181}
 4182
 4183#[gpui::test]
 4184async fn test_indent_yaml_non_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 4185    // This is a test to make sure our fix for issue #33761 didn't break anything
 4186    init_test(cx, |_| {});
 4187
 4188    let mut cx = EditorTestContext::new(cx).await;
 4189    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 4190    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 4191
 4192    cx.set_state(
 4193        r#"ˇingress:
 4194ˇ  api:
 4195ˇ    enabled: false
 4196ˇ    pathType: Prefix
 4197"#,
 4198    );
 4199
 4200    // Press tab to indent all lines
 4201    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4202
 4203    cx.assert_editor_state(
 4204        r#"ˇingress:
 4205    ˇapi:
 4206        ˇenabled: false
 4207        ˇpathType: Prefix
 4208"#,
 4209    );
 4210}
 4211
 4212#[gpui::test]
 4213async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 4214    init_test(cx, |settings| {
 4215        settings.defaults.hard_tabs = Some(true);
 4216    });
 4217
 4218    let mut cx = EditorTestContext::new(cx).await;
 4219
 4220    // select two ranges on one line
 4221    cx.set_state(indoc! {"
 4222        «oneˇ» «twoˇ»
 4223        three
 4224        four
 4225    "});
 4226    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4227    cx.assert_editor_state(indoc! {"
 4228        \t«oneˇ» «twoˇ»
 4229        three
 4230        four
 4231    "});
 4232    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4233    cx.assert_editor_state(indoc! {"
 4234        \t\t«oneˇ» «twoˇ»
 4235        three
 4236        four
 4237    "});
 4238    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4239    cx.assert_editor_state(indoc! {"
 4240        \t«oneˇ» «twoˇ»
 4241        three
 4242        four
 4243    "});
 4244    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4245    cx.assert_editor_state(indoc! {"
 4246        «oneˇ» «twoˇ»
 4247        three
 4248        four
 4249    "});
 4250
 4251    // select across a line ending
 4252    cx.set_state(indoc! {"
 4253        one two
 4254        t«hree
 4255        ˇ»four
 4256    "});
 4257    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4258    cx.assert_editor_state(indoc! {"
 4259        one two
 4260        \tt«hree
 4261        ˇ»four
 4262    "});
 4263    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4264    cx.assert_editor_state(indoc! {"
 4265        one two
 4266        \t\tt«hree
 4267        ˇ»four
 4268    "});
 4269    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4270    cx.assert_editor_state(indoc! {"
 4271        one two
 4272        \tt«hree
 4273        ˇ»four
 4274    "});
 4275    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4276    cx.assert_editor_state(indoc! {"
 4277        one two
 4278        t«hree
 4279        ˇ»four
 4280    "});
 4281
 4282    // Ensure that indenting/outdenting works when the cursor is at column 0.
 4283    cx.set_state(indoc! {"
 4284        one two
 4285        ˇthree
 4286        four
 4287    "});
 4288    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4289    cx.assert_editor_state(indoc! {"
 4290        one two
 4291        ˇthree
 4292        four
 4293    "});
 4294    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4295    cx.assert_editor_state(indoc! {"
 4296        one two
 4297        \tˇthree
 4298        four
 4299    "});
 4300    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4301    cx.assert_editor_state(indoc! {"
 4302        one two
 4303        ˇthree
 4304        four
 4305    "});
 4306}
 4307
 4308#[gpui::test]
 4309fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 4310    init_test(cx, |settings| {
 4311        settings.languages.0.extend([
 4312            (
 4313                "TOML".into(),
 4314                LanguageSettingsContent {
 4315                    tab_size: NonZeroU32::new(2),
 4316                    ..Default::default()
 4317                },
 4318            ),
 4319            (
 4320                "Rust".into(),
 4321                LanguageSettingsContent {
 4322                    tab_size: NonZeroU32::new(4),
 4323                    ..Default::default()
 4324                },
 4325            ),
 4326        ]);
 4327    });
 4328
 4329    let toml_language = Arc::new(Language::new(
 4330        LanguageConfig {
 4331            name: "TOML".into(),
 4332            ..Default::default()
 4333        },
 4334        None,
 4335    ));
 4336    let rust_language = Arc::new(Language::new(
 4337        LanguageConfig {
 4338            name: "Rust".into(),
 4339            ..Default::default()
 4340        },
 4341        None,
 4342    ));
 4343
 4344    let toml_buffer =
 4345        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 4346    let rust_buffer =
 4347        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 4348    let multibuffer = cx.new(|cx| {
 4349        let mut multibuffer = MultiBuffer::new(ReadWrite);
 4350        multibuffer.push_excerpts(
 4351            toml_buffer.clone(),
 4352            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 4353            cx,
 4354        );
 4355        multibuffer.push_excerpts(
 4356            rust_buffer.clone(),
 4357            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 4358            cx,
 4359        );
 4360        multibuffer
 4361    });
 4362
 4363    cx.add_window(|window, cx| {
 4364        let mut editor = build_editor(multibuffer, window, cx);
 4365
 4366        assert_eq!(
 4367            editor.text(cx),
 4368            indoc! {"
 4369                a = 1
 4370                b = 2
 4371
 4372                const c: usize = 3;
 4373            "}
 4374        );
 4375
 4376        select_ranges(
 4377            &mut editor,
 4378            indoc! {"
 4379                «aˇ» = 1
 4380                b = 2
 4381
 4382                «const c:ˇ» usize = 3;
 4383            "},
 4384            window,
 4385            cx,
 4386        );
 4387
 4388        editor.tab(&Tab, window, cx);
 4389        assert_text_with_selections(
 4390            &mut editor,
 4391            indoc! {"
 4392                  «aˇ» = 1
 4393                b = 2
 4394
 4395                    «const c:ˇ» usize = 3;
 4396            "},
 4397            cx,
 4398        );
 4399        editor.backtab(&Backtab, window, cx);
 4400        assert_text_with_selections(
 4401            &mut editor,
 4402            indoc! {"
 4403                «aˇ» = 1
 4404                b = 2
 4405
 4406                «const c:ˇ» usize = 3;
 4407            "},
 4408            cx,
 4409        );
 4410
 4411        editor
 4412    });
 4413}
 4414
 4415#[gpui::test]
 4416async fn test_backspace(cx: &mut TestAppContext) {
 4417    init_test(cx, |_| {});
 4418
 4419    let mut cx = EditorTestContext::new(cx).await;
 4420
 4421    // Basic backspace
 4422    cx.set_state(indoc! {"
 4423        onˇe two three
 4424        fou«rˇ» five six
 4425        seven «ˇeight nine
 4426        »ten
 4427    "});
 4428    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4429    cx.assert_editor_state(indoc! {"
 4430        oˇe two three
 4431        fouˇ five six
 4432        seven ˇten
 4433    "});
 4434
 4435    // Test backspace inside and around indents
 4436    cx.set_state(indoc! {"
 4437        zero
 4438            ˇone
 4439                ˇtwo
 4440            ˇ ˇ ˇ  three
 4441        ˇ  ˇ  four
 4442    "});
 4443    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4444    cx.assert_editor_state(indoc! {"
 4445        zero
 4446        ˇone
 4447            ˇtwo
 4448        ˇ  threeˇ  four
 4449    "});
 4450}
 4451
 4452#[gpui::test]
 4453async fn test_delete(cx: &mut TestAppContext) {
 4454    init_test(cx, |_| {});
 4455
 4456    let mut cx = EditorTestContext::new(cx).await;
 4457    cx.set_state(indoc! {"
 4458        onˇe two three
 4459        fou«rˇ» five six
 4460        seven «ˇeight nine
 4461        »ten
 4462    "});
 4463    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 4464    cx.assert_editor_state(indoc! {"
 4465        onˇ two three
 4466        fouˇ five six
 4467        seven ˇten
 4468    "});
 4469}
 4470
 4471#[gpui::test]
 4472fn test_delete_line(cx: &mut TestAppContext) {
 4473    init_test(cx, |_| {});
 4474
 4475    let editor = cx.add_window(|window, cx| {
 4476        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4477        build_editor(buffer, window, cx)
 4478    });
 4479    _ = editor.update(cx, |editor, window, cx| {
 4480        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4481            s.select_display_ranges([
 4482                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4483                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4484                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4485            ])
 4486        });
 4487        editor.delete_line(&DeleteLine, window, cx);
 4488        assert_eq!(editor.display_text(cx), "ghi");
 4489        assert_eq!(
 4490            editor.selections.display_ranges(cx),
 4491            vec![
 4492                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 4493                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4494            ]
 4495        );
 4496    });
 4497
 4498    let editor = cx.add_window(|window, cx| {
 4499        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4500        build_editor(buffer, window, cx)
 4501    });
 4502    _ = editor.update(cx, |editor, window, cx| {
 4503        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4504            s.select_display_ranges([
 4505                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 4506            ])
 4507        });
 4508        editor.delete_line(&DeleteLine, window, cx);
 4509        assert_eq!(editor.display_text(cx), "ghi\n");
 4510        assert_eq!(
 4511            editor.selections.display_ranges(cx),
 4512            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 4513        );
 4514    });
 4515
 4516    let editor = cx.add_window(|window, cx| {
 4517        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n\njkl\nmno", cx);
 4518        build_editor(buffer, window, cx)
 4519    });
 4520    _ = editor.update(cx, |editor, window, cx| {
 4521        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4522            s.select_display_ranges([
 4523                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(2), 1)
 4524            ])
 4525        });
 4526        editor.delete_line(&DeleteLine, window, cx);
 4527        assert_eq!(editor.display_text(cx), "\njkl\nmno");
 4528        assert_eq!(
 4529            editor.selections.display_ranges(cx),
 4530            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 4531        );
 4532    });
 4533}
 4534
 4535#[gpui::test]
 4536fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 4537    init_test(cx, |_| {});
 4538
 4539    cx.add_window(|window, cx| {
 4540        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4541        let mut editor = build_editor(buffer.clone(), window, cx);
 4542        let buffer = buffer.read(cx).as_singleton().unwrap();
 4543
 4544        assert_eq!(
 4545            editor
 4546                .selections
 4547                .ranges::<Point>(&editor.display_snapshot(cx)),
 4548            &[Point::new(0, 0)..Point::new(0, 0)]
 4549        );
 4550
 4551        // When on single line, replace newline at end by space
 4552        editor.join_lines(&JoinLines, window, cx);
 4553        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4554        assert_eq!(
 4555            editor
 4556                .selections
 4557                .ranges::<Point>(&editor.display_snapshot(cx)),
 4558            &[Point::new(0, 3)..Point::new(0, 3)]
 4559        );
 4560
 4561        // When multiple lines are selected, remove newlines that are spanned by the selection
 4562        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4563            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 4564        });
 4565        editor.join_lines(&JoinLines, window, cx);
 4566        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 4567        assert_eq!(
 4568            editor
 4569                .selections
 4570                .ranges::<Point>(&editor.display_snapshot(cx)),
 4571            &[Point::new(0, 11)..Point::new(0, 11)]
 4572        );
 4573
 4574        // Undo should be transactional
 4575        editor.undo(&Undo, window, cx);
 4576        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4577        assert_eq!(
 4578            editor
 4579                .selections
 4580                .ranges::<Point>(&editor.display_snapshot(cx)),
 4581            &[Point::new(0, 5)..Point::new(2, 2)]
 4582        );
 4583
 4584        // When joining an empty line don't insert a space
 4585        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4586            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 4587        });
 4588        editor.join_lines(&JoinLines, window, cx);
 4589        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 4590        assert_eq!(
 4591            editor
 4592                .selections
 4593                .ranges::<Point>(&editor.display_snapshot(cx)),
 4594            [Point::new(2, 3)..Point::new(2, 3)]
 4595        );
 4596
 4597        // We can remove trailing newlines
 4598        editor.join_lines(&JoinLines, window, cx);
 4599        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4600        assert_eq!(
 4601            editor
 4602                .selections
 4603                .ranges::<Point>(&editor.display_snapshot(cx)),
 4604            [Point::new(2, 3)..Point::new(2, 3)]
 4605        );
 4606
 4607        // We don't blow up on the last line
 4608        editor.join_lines(&JoinLines, window, cx);
 4609        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4610        assert_eq!(
 4611            editor
 4612                .selections
 4613                .ranges::<Point>(&editor.display_snapshot(cx)),
 4614            [Point::new(2, 3)..Point::new(2, 3)]
 4615        );
 4616
 4617        // reset to test indentation
 4618        editor.buffer.update(cx, |buffer, cx| {
 4619            buffer.edit(
 4620                [
 4621                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 4622                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 4623                ],
 4624                None,
 4625                cx,
 4626            )
 4627        });
 4628
 4629        // We remove any leading spaces
 4630        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 4631        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4632            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 4633        });
 4634        editor.join_lines(&JoinLines, window, cx);
 4635        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 4636
 4637        // We don't insert a space for a line containing only spaces
 4638        editor.join_lines(&JoinLines, window, cx);
 4639        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 4640
 4641        // We ignore any leading tabs
 4642        editor.join_lines(&JoinLines, window, cx);
 4643        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 4644
 4645        editor
 4646    });
 4647}
 4648
 4649#[gpui::test]
 4650fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 4651    init_test(cx, |_| {});
 4652
 4653    cx.add_window(|window, cx| {
 4654        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4655        let mut editor = build_editor(buffer.clone(), window, cx);
 4656        let buffer = buffer.read(cx).as_singleton().unwrap();
 4657
 4658        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4659            s.select_ranges([
 4660                Point::new(0, 2)..Point::new(1, 1),
 4661                Point::new(1, 2)..Point::new(1, 2),
 4662                Point::new(3, 1)..Point::new(3, 2),
 4663            ])
 4664        });
 4665
 4666        editor.join_lines(&JoinLines, window, cx);
 4667        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 4668
 4669        assert_eq!(
 4670            editor
 4671                .selections
 4672                .ranges::<Point>(&editor.display_snapshot(cx)),
 4673            [
 4674                Point::new(0, 7)..Point::new(0, 7),
 4675                Point::new(1, 3)..Point::new(1, 3)
 4676            ]
 4677        );
 4678        editor
 4679    });
 4680}
 4681
 4682#[gpui::test]
 4683async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 4684    init_test(cx, |_| {});
 4685
 4686    let mut cx = EditorTestContext::new(cx).await;
 4687
 4688    let diff_base = r#"
 4689        Line 0
 4690        Line 1
 4691        Line 2
 4692        Line 3
 4693        "#
 4694    .unindent();
 4695
 4696    cx.set_state(
 4697        &r#"
 4698        ˇLine 0
 4699        Line 1
 4700        Line 2
 4701        Line 3
 4702        "#
 4703        .unindent(),
 4704    );
 4705
 4706    cx.set_head_text(&diff_base);
 4707    executor.run_until_parked();
 4708
 4709    // Join lines
 4710    cx.update_editor(|editor, window, cx| {
 4711        editor.join_lines(&JoinLines, window, cx);
 4712    });
 4713    executor.run_until_parked();
 4714
 4715    cx.assert_editor_state(
 4716        &r#"
 4717        Line 0ˇ Line 1
 4718        Line 2
 4719        Line 3
 4720        "#
 4721        .unindent(),
 4722    );
 4723    // Join again
 4724    cx.update_editor(|editor, window, cx| {
 4725        editor.join_lines(&JoinLines, window, cx);
 4726    });
 4727    executor.run_until_parked();
 4728
 4729    cx.assert_editor_state(
 4730        &r#"
 4731        Line 0 Line 1ˇ Line 2
 4732        Line 3
 4733        "#
 4734        .unindent(),
 4735    );
 4736}
 4737
 4738#[gpui::test]
 4739async fn test_custom_newlines_cause_no_false_positive_diffs(
 4740    executor: BackgroundExecutor,
 4741    cx: &mut TestAppContext,
 4742) {
 4743    init_test(cx, |_| {});
 4744    let mut cx = EditorTestContext::new(cx).await;
 4745    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 4746    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 4747    executor.run_until_parked();
 4748
 4749    cx.update_editor(|editor, window, cx| {
 4750        let snapshot = editor.snapshot(window, cx);
 4751        assert_eq!(
 4752            snapshot
 4753                .buffer_snapshot()
 4754                .diff_hunks_in_range(0..snapshot.buffer_snapshot().len())
 4755                .collect::<Vec<_>>(),
 4756            Vec::new(),
 4757            "Should not have any diffs for files with custom newlines"
 4758        );
 4759    });
 4760}
 4761
 4762#[gpui::test]
 4763async fn test_manipulate_immutable_lines_with_single_selection(cx: &mut TestAppContext) {
 4764    init_test(cx, |_| {});
 4765
 4766    let mut cx = EditorTestContext::new(cx).await;
 4767
 4768    // Test sort_lines_case_insensitive()
 4769    cx.set_state(indoc! {"
 4770        «z
 4771        y
 4772        x
 4773        Z
 4774        Y
 4775        Xˇ»
 4776    "});
 4777    cx.update_editor(|e, window, cx| {
 4778        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 4779    });
 4780    cx.assert_editor_state(indoc! {"
 4781        «x
 4782        X
 4783        y
 4784        Y
 4785        z
 4786        Zˇ»
 4787    "});
 4788
 4789    // Test sort_lines_by_length()
 4790    //
 4791    // Demonstrates:
 4792    // - ∞ is 3 bytes UTF-8, but sorted by its char count (1)
 4793    // - sort is stable
 4794    cx.set_state(indoc! {"
 4795        «123
 4796        æ
 4797        12
 4798 4799        1
 4800        æˇ»
 4801    "});
 4802    cx.update_editor(|e, window, cx| e.sort_lines_by_length(&SortLinesByLength, window, cx));
 4803    cx.assert_editor_state(indoc! {"
 4804        «æ
 4805 4806        1
 4807        æ
 4808        12
 4809        123ˇ»
 4810    "});
 4811
 4812    // Test reverse_lines()
 4813    cx.set_state(indoc! {"
 4814        «5
 4815        4
 4816        3
 4817        2
 4818        1ˇ»
 4819    "});
 4820    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4821    cx.assert_editor_state(indoc! {"
 4822        «1
 4823        2
 4824        3
 4825        4
 4826        5ˇ»
 4827    "});
 4828
 4829    // Skip testing shuffle_line()
 4830
 4831    // From here on out, test more complex cases of manipulate_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 4832    // Since all methods calling manipulate_immutable_lines() are doing the exact same general thing (reordering lines)
 4833
 4834    // Don't manipulate when cursor is on single line, but expand the selection
 4835    cx.set_state(indoc! {"
 4836        ddˇdd
 4837        ccc
 4838        bb
 4839        a
 4840    "});
 4841    cx.update_editor(|e, window, cx| {
 4842        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4843    });
 4844    cx.assert_editor_state(indoc! {"
 4845        «ddddˇ»
 4846        ccc
 4847        bb
 4848        a
 4849    "});
 4850
 4851    // Basic manipulate case
 4852    // Start selection moves to column 0
 4853    // End of selection shrinks to fit shorter line
 4854    cx.set_state(indoc! {"
 4855        dd«d
 4856        ccc
 4857        bb
 4858        aaaaaˇ»
 4859    "});
 4860    cx.update_editor(|e, window, cx| {
 4861        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4862    });
 4863    cx.assert_editor_state(indoc! {"
 4864        «aaaaa
 4865        bb
 4866        ccc
 4867        dddˇ»
 4868    "});
 4869
 4870    // Manipulate case with newlines
 4871    cx.set_state(indoc! {"
 4872        dd«d
 4873        ccc
 4874
 4875        bb
 4876        aaaaa
 4877
 4878        ˇ»
 4879    "});
 4880    cx.update_editor(|e, window, cx| {
 4881        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4882    });
 4883    cx.assert_editor_state(indoc! {"
 4884        «
 4885
 4886        aaaaa
 4887        bb
 4888        ccc
 4889        dddˇ»
 4890
 4891    "});
 4892
 4893    // Adding new line
 4894    cx.set_state(indoc! {"
 4895        aa«a
 4896        bbˇ»b
 4897    "});
 4898    cx.update_editor(|e, window, cx| {
 4899        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added_line"))
 4900    });
 4901    cx.assert_editor_state(indoc! {"
 4902        «aaa
 4903        bbb
 4904        added_lineˇ»
 4905    "});
 4906
 4907    // Removing line
 4908    cx.set_state(indoc! {"
 4909        aa«a
 4910        bbbˇ»
 4911    "});
 4912    cx.update_editor(|e, window, cx| {
 4913        e.manipulate_immutable_lines(window, cx, |lines| {
 4914            lines.pop();
 4915        })
 4916    });
 4917    cx.assert_editor_state(indoc! {"
 4918        «aaaˇ»
 4919    "});
 4920
 4921    // Removing all lines
 4922    cx.set_state(indoc! {"
 4923        aa«a
 4924        bbbˇ»
 4925    "});
 4926    cx.update_editor(|e, window, cx| {
 4927        e.manipulate_immutable_lines(window, cx, |lines| {
 4928            lines.drain(..);
 4929        })
 4930    });
 4931    cx.assert_editor_state(indoc! {"
 4932        ˇ
 4933    "});
 4934}
 4935
 4936#[gpui::test]
 4937async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 4938    init_test(cx, |_| {});
 4939
 4940    let mut cx = EditorTestContext::new(cx).await;
 4941
 4942    // Consider continuous selection as single selection
 4943    cx.set_state(indoc! {"
 4944        Aaa«aa
 4945        cˇ»c«c
 4946        bb
 4947        aaaˇ»aa
 4948    "});
 4949    cx.update_editor(|e, window, cx| {
 4950        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4951    });
 4952    cx.assert_editor_state(indoc! {"
 4953        «Aaaaa
 4954        ccc
 4955        bb
 4956        aaaaaˇ»
 4957    "});
 4958
 4959    cx.set_state(indoc! {"
 4960        Aaa«aa
 4961        cˇ»c«c
 4962        bb
 4963        aaaˇ»aa
 4964    "});
 4965    cx.update_editor(|e, window, cx| {
 4966        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4967    });
 4968    cx.assert_editor_state(indoc! {"
 4969        «Aaaaa
 4970        ccc
 4971        bbˇ»
 4972    "});
 4973
 4974    // Consider non continuous selection as distinct dedup operations
 4975    cx.set_state(indoc! {"
 4976        «aaaaa
 4977        bb
 4978        aaaaa
 4979        aaaaaˇ»
 4980
 4981        aaa«aaˇ»
 4982    "});
 4983    cx.update_editor(|e, window, cx| {
 4984        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4985    });
 4986    cx.assert_editor_state(indoc! {"
 4987        «aaaaa
 4988        bbˇ»
 4989
 4990        «aaaaaˇ»
 4991    "});
 4992}
 4993
 4994#[gpui::test]
 4995async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 4996    init_test(cx, |_| {});
 4997
 4998    let mut cx = EditorTestContext::new(cx).await;
 4999
 5000    cx.set_state(indoc! {"
 5001        «Aaa
 5002        aAa
 5003        Aaaˇ»
 5004    "});
 5005    cx.update_editor(|e, window, cx| {
 5006        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 5007    });
 5008    cx.assert_editor_state(indoc! {"
 5009        «Aaa
 5010        aAaˇ»
 5011    "});
 5012
 5013    cx.set_state(indoc! {"
 5014        «Aaa
 5015        aAa
 5016        aaAˇ»
 5017    "});
 5018    cx.update_editor(|e, window, cx| {
 5019        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 5020    });
 5021    cx.assert_editor_state(indoc! {"
 5022        «Aaaˇ»
 5023    "});
 5024}
 5025
 5026#[gpui::test]
 5027async fn test_wrap_in_tag_single_selection(cx: &mut TestAppContext) {
 5028    init_test(cx, |_| {});
 5029
 5030    let mut cx = EditorTestContext::new(cx).await;
 5031
 5032    let js_language = Arc::new(Language::new(
 5033        LanguageConfig {
 5034            name: "JavaScript".into(),
 5035            wrap_characters: Some(language::WrapCharactersConfig {
 5036                start_prefix: "<".into(),
 5037                start_suffix: ">".into(),
 5038                end_prefix: "</".into(),
 5039                end_suffix: ">".into(),
 5040            }),
 5041            ..LanguageConfig::default()
 5042        },
 5043        None,
 5044    ));
 5045
 5046    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 5047
 5048    cx.set_state(indoc! {"
 5049        «testˇ»
 5050    "});
 5051    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5052    cx.assert_editor_state(indoc! {"
 5053        <«ˇ»>test</«ˇ»>
 5054    "});
 5055
 5056    cx.set_state(indoc! {"
 5057        «test
 5058         testˇ»
 5059    "});
 5060    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5061    cx.assert_editor_state(indoc! {"
 5062        <«ˇ»>test
 5063         test</«ˇ»>
 5064    "});
 5065
 5066    cx.set_state(indoc! {"
 5067        teˇst
 5068    "});
 5069    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5070    cx.assert_editor_state(indoc! {"
 5071        te<«ˇ»></«ˇ»>st
 5072    "});
 5073}
 5074
 5075#[gpui::test]
 5076async fn test_wrap_in_tag_multi_selection(cx: &mut TestAppContext) {
 5077    init_test(cx, |_| {});
 5078
 5079    let mut cx = EditorTestContext::new(cx).await;
 5080
 5081    let js_language = Arc::new(Language::new(
 5082        LanguageConfig {
 5083            name: "JavaScript".into(),
 5084            wrap_characters: Some(language::WrapCharactersConfig {
 5085                start_prefix: "<".into(),
 5086                start_suffix: ">".into(),
 5087                end_prefix: "</".into(),
 5088                end_suffix: ">".into(),
 5089            }),
 5090            ..LanguageConfig::default()
 5091        },
 5092        None,
 5093    ));
 5094
 5095    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 5096
 5097    cx.set_state(indoc! {"
 5098        «testˇ»
 5099        «testˇ» «testˇ»
 5100        «testˇ»
 5101    "});
 5102    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5103    cx.assert_editor_state(indoc! {"
 5104        <«ˇ»>test</«ˇ»>
 5105        <«ˇ»>test</«ˇ»> <«ˇ»>test</«ˇ»>
 5106        <«ˇ»>test</«ˇ»>
 5107    "});
 5108
 5109    cx.set_state(indoc! {"
 5110        «test
 5111         testˇ»
 5112        «test
 5113         testˇ»
 5114    "});
 5115    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5116    cx.assert_editor_state(indoc! {"
 5117        <«ˇ»>test
 5118         test</«ˇ»>
 5119        <«ˇ»>test
 5120         test</«ˇ»>
 5121    "});
 5122}
 5123
 5124#[gpui::test]
 5125async fn test_wrap_in_tag_does_nothing_in_unsupported_languages(cx: &mut TestAppContext) {
 5126    init_test(cx, |_| {});
 5127
 5128    let mut cx = EditorTestContext::new(cx).await;
 5129
 5130    let plaintext_language = Arc::new(Language::new(
 5131        LanguageConfig {
 5132            name: "Plain Text".into(),
 5133            ..LanguageConfig::default()
 5134        },
 5135        None,
 5136    ));
 5137
 5138    cx.update_buffer(|buffer, cx| buffer.set_language(Some(plaintext_language), cx));
 5139
 5140    cx.set_state(indoc! {"
 5141        «testˇ»
 5142    "});
 5143    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5144    cx.assert_editor_state(indoc! {"
 5145      «testˇ»
 5146    "});
 5147}
 5148
 5149#[gpui::test]
 5150async fn test_manipulate_immutable_lines_with_multi_selection(cx: &mut TestAppContext) {
 5151    init_test(cx, |_| {});
 5152
 5153    let mut cx = EditorTestContext::new(cx).await;
 5154
 5155    // Manipulate with multiple selections on a single line
 5156    cx.set_state(indoc! {"
 5157        dd«dd
 5158        cˇ»c«c
 5159        bb
 5160        aaaˇ»aa
 5161    "});
 5162    cx.update_editor(|e, window, cx| {
 5163        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 5164    });
 5165    cx.assert_editor_state(indoc! {"
 5166        «aaaaa
 5167        bb
 5168        ccc
 5169        ddddˇ»
 5170    "});
 5171
 5172    // Manipulate with multiple disjoin selections
 5173    cx.set_state(indoc! {"
 5174 5175        4
 5176        3
 5177        2
 5178        1ˇ»
 5179
 5180        dd«dd
 5181        ccc
 5182        bb
 5183        aaaˇ»aa
 5184    "});
 5185    cx.update_editor(|e, window, cx| {
 5186        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 5187    });
 5188    cx.assert_editor_state(indoc! {"
 5189        «1
 5190        2
 5191        3
 5192        4
 5193        5ˇ»
 5194
 5195        «aaaaa
 5196        bb
 5197        ccc
 5198        ddddˇ»
 5199    "});
 5200
 5201    // Adding lines on each selection
 5202    cx.set_state(indoc! {"
 5203 5204        1ˇ»
 5205
 5206        bb«bb
 5207        aaaˇ»aa
 5208    "});
 5209    cx.update_editor(|e, window, cx| {
 5210        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added line"))
 5211    });
 5212    cx.assert_editor_state(indoc! {"
 5213        «2
 5214        1
 5215        added lineˇ»
 5216
 5217        «bbbb
 5218        aaaaa
 5219        added lineˇ»
 5220    "});
 5221
 5222    // Removing lines on each selection
 5223    cx.set_state(indoc! {"
 5224 5225        1ˇ»
 5226
 5227        bb«bb
 5228        aaaˇ»aa
 5229    "});
 5230    cx.update_editor(|e, window, cx| {
 5231        e.manipulate_immutable_lines(window, cx, |lines| {
 5232            lines.pop();
 5233        })
 5234    });
 5235    cx.assert_editor_state(indoc! {"
 5236        «2ˇ»
 5237
 5238        «bbbbˇ»
 5239    "});
 5240}
 5241
 5242#[gpui::test]
 5243async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 5244    init_test(cx, |settings| {
 5245        settings.defaults.tab_size = NonZeroU32::new(3)
 5246    });
 5247
 5248    let mut cx = EditorTestContext::new(cx).await;
 5249
 5250    // MULTI SELECTION
 5251    // Ln.1 "«" tests empty lines
 5252    // Ln.9 tests just leading whitespace
 5253    cx.set_state(indoc! {"
 5254        «
 5255        abc                 // No indentationˇ»
 5256        «\tabc              // 1 tabˇ»
 5257        \t\tabc «      ˇ»   // 2 tabs
 5258        \t ab«c             // Tab followed by space
 5259         \tabc              // Space followed by tab (3 spaces should be the result)
 5260        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5261           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 5262        \t
 5263        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5264    "});
 5265    cx.update_editor(|e, window, cx| {
 5266        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5267    });
 5268    cx.assert_editor_state(
 5269        indoc! {"
 5270            «
 5271            abc                 // No indentation
 5272               abc              // 1 tab
 5273                  abc          // 2 tabs
 5274                abc             // Tab followed by space
 5275               abc              // Space followed by tab (3 spaces should be the result)
 5276                           abc   // Mixed indentation (tab conversion depends on the column)
 5277               abc         // Already space indented
 5278               ·
 5279               abc\tdef          // Only the leading tab is manipulatedˇ»
 5280        "}
 5281        .replace("·", "")
 5282        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5283    );
 5284
 5285    // Test on just a few lines, the others should remain unchanged
 5286    // Only lines (3, 5, 10, 11) should change
 5287    cx.set_state(
 5288        indoc! {"
 5289            ·
 5290            abc                 // No indentation
 5291            \tabcˇ               // 1 tab
 5292            \t\tabc             // 2 tabs
 5293            \t abcˇ              // Tab followed by space
 5294             \tabc              // Space followed by tab (3 spaces should be the result)
 5295            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5296               abc              // Already space indented
 5297            «\t
 5298            \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5299        "}
 5300        .replace("·", "")
 5301        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5302    );
 5303    cx.update_editor(|e, window, cx| {
 5304        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5305    });
 5306    cx.assert_editor_state(
 5307        indoc! {"
 5308            ·
 5309            abc                 // No indentation
 5310            «   abc               // 1 tabˇ»
 5311            \t\tabc             // 2 tabs
 5312            «    abc              // Tab followed by spaceˇ»
 5313             \tabc              // Space followed by tab (3 spaces should be the result)
 5314            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5315               abc              // Already space indented
 5316            «   ·
 5317               abc\tdef          // Only the leading tab is manipulatedˇ»
 5318        "}
 5319        .replace("·", "")
 5320        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5321    );
 5322
 5323    // SINGLE SELECTION
 5324    // Ln.1 "«" tests empty lines
 5325    // Ln.9 tests just leading whitespace
 5326    cx.set_state(indoc! {"
 5327        «
 5328        abc                 // No indentation
 5329        \tabc               // 1 tab
 5330        \t\tabc             // 2 tabs
 5331        \t abc              // Tab followed by space
 5332         \tabc              // Space followed by tab (3 spaces should be the result)
 5333        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5334           abc              // Already space indented
 5335        \t
 5336        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5337    "});
 5338    cx.update_editor(|e, window, cx| {
 5339        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5340    });
 5341    cx.assert_editor_state(
 5342        indoc! {"
 5343            «
 5344            abc                 // No indentation
 5345               abc               // 1 tab
 5346                  abc             // 2 tabs
 5347                abc              // Tab followed by space
 5348               abc              // Space followed by tab (3 spaces should be the result)
 5349                           abc   // Mixed indentation (tab conversion depends on the column)
 5350               abc              // Already space indented
 5351               ·
 5352               abc\tdef          // Only the leading tab is manipulatedˇ»
 5353        "}
 5354        .replace("·", "")
 5355        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5356    );
 5357}
 5358
 5359#[gpui::test]
 5360async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 5361    init_test(cx, |settings| {
 5362        settings.defaults.tab_size = NonZeroU32::new(3)
 5363    });
 5364
 5365    let mut cx = EditorTestContext::new(cx).await;
 5366
 5367    // MULTI SELECTION
 5368    // Ln.1 "«" tests empty lines
 5369    // Ln.11 tests just leading whitespace
 5370    cx.set_state(indoc! {"
 5371        «
 5372        abˇ»ˇc                 // No indentation
 5373         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 5374          abc  «             // 2 spaces (< 3 so dont convert)
 5375           abc              // 3 spaces (convert)
 5376             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 5377        «\tˇ»\t«\tˇ»abc           // Already tab indented
 5378        «\t abc              // Tab followed by space
 5379         \tabc              // Space followed by tab (should be consumed due to tab)
 5380        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5381           \tˇ»  «\t
 5382           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 5383    "});
 5384    cx.update_editor(|e, window, cx| {
 5385        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5386    });
 5387    cx.assert_editor_state(indoc! {"
 5388        «
 5389        abc                 // No indentation
 5390         abc                // 1 space (< 3 so dont convert)
 5391          abc               // 2 spaces (< 3 so dont convert)
 5392        \tabc              // 3 spaces (convert)
 5393        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5394        \t\t\tabc           // Already tab indented
 5395        \t abc              // Tab followed by space
 5396        \tabc              // Space followed by tab (should be consumed due to tab)
 5397        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5398        \t\t\t
 5399        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5400    "});
 5401
 5402    // Test on just a few lines, the other should remain unchanged
 5403    // Only lines (4, 8, 11, 12) should change
 5404    cx.set_state(
 5405        indoc! {"
 5406            ·
 5407            abc                 // No indentation
 5408             abc                // 1 space (< 3 so dont convert)
 5409              abc               // 2 spaces (< 3 so dont convert)
 5410            «   abc              // 3 spaces (convert)ˇ»
 5411                 abc            // 5 spaces (1 tab + 2 spaces)
 5412            \t\t\tabc           // Already tab indented
 5413            \t abc              // Tab followed by space
 5414             \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 5415               \t\t  \tabc      // Mixed indentation
 5416            \t \t  \t   \tabc   // Mixed indentation
 5417               \t  \tˇ
 5418            «   abc   \t         // Only the leading spaces should be convertedˇ»
 5419        "}
 5420        .replace("·", "")
 5421        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5422    );
 5423    cx.update_editor(|e, window, cx| {
 5424        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5425    });
 5426    cx.assert_editor_state(
 5427        indoc! {"
 5428            ·
 5429            abc                 // No indentation
 5430             abc                // 1 space (< 3 so dont convert)
 5431              abc               // 2 spaces (< 3 so dont convert)
 5432            «\tabc              // 3 spaces (convert)ˇ»
 5433                 abc            // 5 spaces (1 tab + 2 spaces)
 5434            \t\t\tabc           // Already tab indented
 5435            \t abc              // Tab followed by space
 5436            «\tabc              // Space followed by tab (should be consumed due to tab)ˇ»
 5437               \t\t  \tabc      // Mixed indentation
 5438            \t \t  \t   \tabc   // Mixed indentation
 5439            «\t\t\t
 5440            \tabc   \t         // Only the leading spaces should be convertedˇ»
 5441        "}
 5442        .replace("·", "")
 5443        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5444    );
 5445
 5446    // SINGLE SELECTION
 5447    // Ln.1 "«" tests empty lines
 5448    // Ln.11 tests just leading whitespace
 5449    cx.set_state(indoc! {"
 5450        «
 5451        abc                 // No indentation
 5452         abc                // 1 space (< 3 so dont convert)
 5453          abc               // 2 spaces (< 3 so dont convert)
 5454           abc              // 3 spaces (convert)
 5455             abc            // 5 spaces (1 tab + 2 spaces)
 5456        \t\t\tabc           // Already tab indented
 5457        \t abc              // Tab followed by space
 5458         \tabc              // Space followed by tab (should be consumed due to tab)
 5459        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5460           \t  \t
 5461           abc   \t         // Only the leading spaces should be convertedˇ»
 5462    "});
 5463    cx.update_editor(|e, window, cx| {
 5464        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5465    });
 5466    cx.assert_editor_state(indoc! {"
 5467        «
 5468        abc                 // No indentation
 5469         abc                // 1 space (< 3 so dont convert)
 5470          abc               // 2 spaces (< 3 so dont convert)
 5471        \tabc              // 3 spaces (convert)
 5472        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5473        \t\t\tabc           // Already tab indented
 5474        \t abc              // Tab followed by space
 5475        \tabc              // Space followed by tab (should be consumed due to tab)
 5476        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5477        \t\t\t
 5478        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5479    "});
 5480}
 5481
 5482#[gpui::test]
 5483async fn test_toggle_case(cx: &mut TestAppContext) {
 5484    init_test(cx, |_| {});
 5485
 5486    let mut cx = EditorTestContext::new(cx).await;
 5487
 5488    // If all lower case -> upper case
 5489    cx.set_state(indoc! {"
 5490        «hello worldˇ»
 5491    "});
 5492    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5493    cx.assert_editor_state(indoc! {"
 5494        «HELLO WORLDˇ»
 5495    "});
 5496
 5497    // If all upper case -> lower case
 5498    cx.set_state(indoc! {"
 5499        «HELLO WORLDˇ»
 5500    "});
 5501    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5502    cx.assert_editor_state(indoc! {"
 5503        «hello worldˇ»
 5504    "});
 5505
 5506    // If any upper case characters are identified -> lower case
 5507    // This matches JetBrains IDEs
 5508    cx.set_state(indoc! {"
 5509        «hEllo worldˇ»
 5510    "});
 5511    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5512    cx.assert_editor_state(indoc! {"
 5513        «hello worldˇ»
 5514    "});
 5515}
 5516
 5517#[gpui::test]
 5518async fn test_convert_to_sentence_case(cx: &mut TestAppContext) {
 5519    init_test(cx, |_| {});
 5520
 5521    let mut cx = EditorTestContext::new(cx).await;
 5522
 5523    cx.set_state(indoc! {"
 5524        «implement-windows-supportˇ»
 5525    "});
 5526    cx.update_editor(|e, window, cx| {
 5527        e.convert_to_sentence_case(&ConvertToSentenceCase, window, cx)
 5528    });
 5529    cx.assert_editor_state(indoc! {"
 5530        «Implement windows supportˇ»
 5531    "});
 5532}
 5533
 5534#[gpui::test]
 5535async fn test_manipulate_text(cx: &mut TestAppContext) {
 5536    init_test(cx, |_| {});
 5537
 5538    let mut cx = EditorTestContext::new(cx).await;
 5539
 5540    // Test convert_to_upper_case()
 5541    cx.set_state(indoc! {"
 5542        «hello worldˇ»
 5543    "});
 5544    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5545    cx.assert_editor_state(indoc! {"
 5546        «HELLO WORLDˇ»
 5547    "});
 5548
 5549    // Test convert_to_lower_case()
 5550    cx.set_state(indoc! {"
 5551        «HELLO WORLDˇ»
 5552    "});
 5553    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 5554    cx.assert_editor_state(indoc! {"
 5555        «hello worldˇ»
 5556    "});
 5557
 5558    // Test multiple line, single selection case
 5559    cx.set_state(indoc! {"
 5560        «The quick brown
 5561        fox jumps over
 5562        the lazy dogˇ»
 5563    "});
 5564    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 5565    cx.assert_editor_state(indoc! {"
 5566        «The Quick Brown
 5567        Fox Jumps Over
 5568        The Lazy Dogˇ»
 5569    "});
 5570
 5571    // Test multiple line, single selection case
 5572    cx.set_state(indoc! {"
 5573        «The quick brown
 5574        fox jumps over
 5575        the lazy dogˇ»
 5576    "});
 5577    cx.update_editor(|e, window, cx| {
 5578        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 5579    });
 5580    cx.assert_editor_state(indoc! {"
 5581        «TheQuickBrown
 5582        FoxJumpsOver
 5583        TheLazyDogˇ»
 5584    "});
 5585
 5586    // From here on out, test more complex cases of manipulate_text()
 5587
 5588    // Test no selection case - should affect words cursors are in
 5589    // Cursor at beginning, middle, and end of word
 5590    cx.set_state(indoc! {"
 5591        ˇhello big beauˇtiful worldˇ
 5592    "});
 5593    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5594    cx.assert_editor_state(indoc! {"
 5595        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 5596    "});
 5597
 5598    // Test multiple selections on a single line and across multiple lines
 5599    cx.set_state(indoc! {"
 5600        «Theˇ» quick «brown
 5601        foxˇ» jumps «overˇ»
 5602        the «lazyˇ» dog
 5603    "});
 5604    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5605    cx.assert_editor_state(indoc! {"
 5606        «THEˇ» quick «BROWN
 5607        FOXˇ» jumps «OVERˇ»
 5608        the «LAZYˇ» dog
 5609    "});
 5610
 5611    // Test case where text length grows
 5612    cx.set_state(indoc! {"
 5613        «tschüߡ»
 5614    "});
 5615    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5616    cx.assert_editor_state(indoc! {"
 5617        «TSCHÜSSˇ»
 5618    "});
 5619
 5620    // Test to make sure we don't crash when text shrinks
 5621    cx.set_state(indoc! {"
 5622        aaa_bbbˇ
 5623    "});
 5624    cx.update_editor(|e, window, cx| {
 5625        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5626    });
 5627    cx.assert_editor_state(indoc! {"
 5628        «aaaBbbˇ»
 5629    "});
 5630
 5631    // Test to make sure we all aware of the fact that each word can grow and shrink
 5632    // Final selections should be aware of this fact
 5633    cx.set_state(indoc! {"
 5634        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 5635    "});
 5636    cx.update_editor(|e, window, cx| {
 5637        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5638    });
 5639    cx.assert_editor_state(indoc! {"
 5640        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 5641    "});
 5642
 5643    cx.set_state(indoc! {"
 5644        «hElLo, WoRld!ˇ»
 5645    "});
 5646    cx.update_editor(|e, window, cx| {
 5647        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 5648    });
 5649    cx.assert_editor_state(indoc! {"
 5650        «HeLlO, wOrLD!ˇ»
 5651    "});
 5652
 5653    // Test selections with `line_mode() = true`.
 5654    cx.update_editor(|editor, _window, _cx| editor.selections.set_line_mode(true));
 5655    cx.set_state(indoc! {"
 5656        «The quick brown
 5657        fox jumps over
 5658        tˇ»he lazy dog
 5659    "});
 5660    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5661    cx.assert_editor_state(indoc! {"
 5662        «THE QUICK BROWN
 5663        FOX JUMPS OVER
 5664        THE LAZY DOGˇ»
 5665    "});
 5666}
 5667
 5668#[gpui::test]
 5669fn test_duplicate_line(cx: &mut TestAppContext) {
 5670    init_test(cx, |_| {});
 5671
 5672    let editor = cx.add_window(|window, cx| {
 5673        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5674        build_editor(buffer, window, cx)
 5675    });
 5676    _ = editor.update(cx, |editor, window, cx| {
 5677        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5678            s.select_display_ranges([
 5679                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5680                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5681                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5682                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5683            ])
 5684        });
 5685        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5686        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5687        assert_eq!(
 5688            editor.selections.display_ranges(cx),
 5689            vec![
 5690                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 5691                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 5692                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5693                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 5694            ]
 5695        );
 5696    });
 5697
 5698    let editor = cx.add_window(|window, cx| {
 5699        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5700        build_editor(buffer, window, cx)
 5701    });
 5702    _ = editor.update(cx, |editor, window, cx| {
 5703        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5704            s.select_display_ranges([
 5705                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5706                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5707            ])
 5708        });
 5709        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5710        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5711        assert_eq!(
 5712            editor.selections.display_ranges(cx),
 5713            vec![
 5714                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 5715                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 5716            ]
 5717        );
 5718    });
 5719
 5720    // With `duplicate_line_up` the selections move to the duplicated lines,
 5721    // which are inserted above the original lines
 5722    let editor = cx.add_window(|window, cx| {
 5723        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5724        build_editor(buffer, window, cx)
 5725    });
 5726    _ = editor.update(cx, |editor, window, cx| {
 5727        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5728            s.select_display_ranges([
 5729                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5730                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5731                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5732                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5733            ])
 5734        });
 5735        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5736        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5737        assert_eq!(
 5738            editor.selections.display_ranges(cx),
 5739            vec![
 5740                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5741                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5742                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 5743                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0),
 5744            ]
 5745        );
 5746    });
 5747
 5748    let editor = cx.add_window(|window, cx| {
 5749        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5750        build_editor(buffer, window, cx)
 5751    });
 5752    _ = editor.update(cx, |editor, window, cx| {
 5753        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5754            s.select_display_ranges([
 5755                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5756                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5757            ])
 5758        });
 5759        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5760        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5761        assert_eq!(
 5762            editor.selections.display_ranges(cx),
 5763            vec![
 5764                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5765                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5766            ]
 5767        );
 5768    });
 5769
 5770    let editor = cx.add_window(|window, cx| {
 5771        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5772        build_editor(buffer, window, cx)
 5773    });
 5774    _ = editor.update(cx, |editor, window, cx| {
 5775        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5776            s.select_display_ranges([
 5777                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5778                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5779            ])
 5780        });
 5781        editor.duplicate_selection(&DuplicateSelection, window, cx);
 5782        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 5783        assert_eq!(
 5784            editor.selections.display_ranges(cx),
 5785            vec![
 5786                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5787                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 5788            ]
 5789        );
 5790    });
 5791}
 5792
 5793#[gpui::test]
 5794fn test_move_line_up_down(cx: &mut TestAppContext) {
 5795    init_test(cx, |_| {});
 5796
 5797    let editor = cx.add_window(|window, cx| {
 5798        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5799        build_editor(buffer, window, cx)
 5800    });
 5801    _ = editor.update(cx, |editor, window, cx| {
 5802        editor.fold_creases(
 5803            vec![
 5804                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 5805                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 5806                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 5807            ],
 5808            true,
 5809            window,
 5810            cx,
 5811        );
 5812        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5813            s.select_display_ranges([
 5814                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5815                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5816                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5817                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 5818            ])
 5819        });
 5820        assert_eq!(
 5821            editor.display_text(cx),
 5822            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 5823        );
 5824
 5825        editor.move_line_up(&MoveLineUp, window, cx);
 5826        assert_eq!(
 5827            editor.display_text(cx),
 5828            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 5829        );
 5830        assert_eq!(
 5831            editor.selections.display_ranges(cx),
 5832            vec![
 5833                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5834                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5835                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5836                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5837            ]
 5838        );
 5839    });
 5840
 5841    _ = editor.update(cx, |editor, window, cx| {
 5842        editor.move_line_down(&MoveLineDown, window, cx);
 5843        assert_eq!(
 5844            editor.display_text(cx),
 5845            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 5846        );
 5847        assert_eq!(
 5848            editor.selections.display_ranges(cx),
 5849            vec![
 5850                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5851                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5852                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5853                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5854            ]
 5855        );
 5856    });
 5857
 5858    _ = editor.update(cx, |editor, window, cx| {
 5859        editor.move_line_down(&MoveLineDown, window, cx);
 5860        assert_eq!(
 5861            editor.display_text(cx),
 5862            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 5863        );
 5864        assert_eq!(
 5865            editor.selections.display_ranges(cx),
 5866            vec![
 5867                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5868                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5869                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5870                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5871            ]
 5872        );
 5873    });
 5874
 5875    _ = editor.update(cx, |editor, window, cx| {
 5876        editor.move_line_up(&MoveLineUp, window, cx);
 5877        assert_eq!(
 5878            editor.display_text(cx),
 5879            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 5880        );
 5881        assert_eq!(
 5882            editor.selections.display_ranges(cx),
 5883            vec![
 5884                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5885                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5886                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5887                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5888            ]
 5889        );
 5890    });
 5891}
 5892
 5893#[gpui::test]
 5894fn test_move_line_up_selection_at_end_of_fold(cx: &mut TestAppContext) {
 5895    init_test(cx, |_| {});
 5896    let editor = cx.add_window(|window, cx| {
 5897        let buffer = MultiBuffer::build_simple("\n\n\n\n\n\naaaa\nbbbb\ncccc", cx);
 5898        build_editor(buffer, window, cx)
 5899    });
 5900    _ = editor.update(cx, |editor, window, cx| {
 5901        editor.fold_creases(
 5902            vec![Crease::simple(
 5903                Point::new(6, 4)..Point::new(7, 4),
 5904                FoldPlaceholder::test(),
 5905            )],
 5906            true,
 5907            window,
 5908            cx,
 5909        );
 5910        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5911            s.select_ranges([Point::new(7, 4)..Point::new(7, 4)])
 5912        });
 5913        assert_eq!(editor.display_text(cx), "\n\n\n\n\n\naaaa⋯\ncccc");
 5914        editor.move_line_up(&MoveLineUp, window, cx);
 5915        let buffer_text = editor.buffer.read(cx).snapshot(cx).text();
 5916        assert_eq!(buffer_text, "\n\n\n\n\naaaa\nbbbb\n\ncccc");
 5917    });
 5918}
 5919
 5920#[gpui::test]
 5921fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 5922    init_test(cx, |_| {});
 5923
 5924    let editor = cx.add_window(|window, cx| {
 5925        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5926        build_editor(buffer, window, cx)
 5927    });
 5928    _ = editor.update(cx, |editor, window, cx| {
 5929        let snapshot = editor.buffer.read(cx).snapshot(cx);
 5930        editor.insert_blocks(
 5931            [BlockProperties {
 5932                style: BlockStyle::Fixed,
 5933                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 5934                height: Some(1),
 5935                render: Arc::new(|_| div().into_any()),
 5936                priority: 0,
 5937            }],
 5938            Some(Autoscroll::fit()),
 5939            cx,
 5940        );
 5941        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5942            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 5943        });
 5944        editor.move_line_down(&MoveLineDown, window, cx);
 5945    });
 5946}
 5947
 5948#[gpui::test]
 5949async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 5950    init_test(cx, |_| {});
 5951
 5952    let mut cx = EditorTestContext::new(cx).await;
 5953    cx.set_state(
 5954        &"
 5955            ˇzero
 5956            one
 5957            two
 5958            three
 5959            four
 5960            five
 5961        "
 5962        .unindent(),
 5963    );
 5964
 5965    // Create a four-line block that replaces three lines of text.
 5966    cx.update_editor(|editor, window, cx| {
 5967        let snapshot = editor.snapshot(window, cx);
 5968        let snapshot = &snapshot.buffer_snapshot();
 5969        let placement = BlockPlacement::Replace(
 5970            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 5971        );
 5972        editor.insert_blocks(
 5973            [BlockProperties {
 5974                placement,
 5975                height: Some(4),
 5976                style: BlockStyle::Sticky,
 5977                render: Arc::new(|_| gpui::div().into_any_element()),
 5978                priority: 0,
 5979            }],
 5980            None,
 5981            cx,
 5982        );
 5983    });
 5984
 5985    // Move down so that the cursor touches the block.
 5986    cx.update_editor(|editor, window, cx| {
 5987        editor.move_down(&Default::default(), window, cx);
 5988    });
 5989    cx.assert_editor_state(
 5990        &"
 5991            zero
 5992            «one
 5993            two
 5994            threeˇ»
 5995            four
 5996            five
 5997        "
 5998        .unindent(),
 5999    );
 6000
 6001    // Move down past the block.
 6002    cx.update_editor(|editor, window, cx| {
 6003        editor.move_down(&Default::default(), window, cx);
 6004    });
 6005    cx.assert_editor_state(
 6006        &"
 6007            zero
 6008            one
 6009            two
 6010            three
 6011            ˇfour
 6012            five
 6013        "
 6014        .unindent(),
 6015    );
 6016}
 6017
 6018#[gpui::test]
 6019fn test_transpose(cx: &mut TestAppContext) {
 6020    init_test(cx, |_| {});
 6021
 6022    _ = cx.add_window(|window, cx| {
 6023        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 6024        editor.set_style(EditorStyle::default(), window, cx);
 6025        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6026            s.select_ranges([1..1])
 6027        });
 6028        editor.transpose(&Default::default(), window, cx);
 6029        assert_eq!(editor.text(cx), "bac");
 6030        assert_eq!(
 6031            editor.selections.ranges(&editor.display_snapshot(cx)),
 6032            [2..2]
 6033        );
 6034
 6035        editor.transpose(&Default::default(), window, cx);
 6036        assert_eq!(editor.text(cx), "bca");
 6037        assert_eq!(
 6038            editor.selections.ranges(&editor.display_snapshot(cx)),
 6039            [3..3]
 6040        );
 6041
 6042        editor.transpose(&Default::default(), window, cx);
 6043        assert_eq!(editor.text(cx), "bac");
 6044        assert_eq!(
 6045            editor.selections.ranges(&editor.display_snapshot(cx)),
 6046            [3..3]
 6047        );
 6048
 6049        editor
 6050    });
 6051
 6052    _ = cx.add_window(|window, cx| {
 6053        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 6054        editor.set_style(EditorStyle::default(), window, cx);
 6055        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6056            s.select_ranges([3..3])
 6057        });
 6058        editor.transpose(&Default::default(), window, cx);
 6059        assert_eq!(editor.text(cx), "acb\nde");
 6060        assert_eq!(
 6061            editor.selections.ranges(&editor.display_snapshot(cx)),
 6062            [3..3]
 6063        );
 6064
 6065        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6066            s.select_ranges([4..4])
 6067        });
 6068        editor.transpose(&Default::default(), window, cx);
 6069        assert_eq!(editor.text(cx), "acbd\ne");
 6070        assert_eq!(
 6071            editor.selections.ranges(&editor.display_snapshot(cx)),
 6072            [5..5]
 6073        );
 6074
 6075        editor.transpose(&Default::default(), window, cx);
 6076        assert_eq!(editor.text(cx), "acbde\n");
 6077        assert_eq!(
 6078            editor.selections.ranges(&editor.display_snapshot(cx)),
 6079            [6..6]
 6080        );
 6081
 6082        editor.transpose(&Default::default(), window, cx);
 6083        assert_eq!(editor.text(cx), "acbd\ne");
 6084        assert_eq!(
 6085            editor.selections.ranges(&editor.display_snapshot(cx)),
 6086            [6..6]
 6087        );
 6088
 6089        editor
 6090    });
 6091
 6092    _ = cx.add_window(|window, cx| {
 6093        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 6094        editor.set_style(EditorStyle::default(), window, cx);
 6095        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6096            s.select_ranges([1..1, 2..2, 4..4])
 6097        });
 6098        editor.transpose(&Default::default(), window, cx);
 6099        assert_eq!(editor.text(cx), "bacd\ne");
 6100        assert_eq!(
 6101            editor.selections.ranges(&editor.display_snapshot(cx)),
 6102            [2..2, 3..3, 5..5]
 6103        );
 6104
 6105        editor.transpose(&Default::default(), window, cx);
 6106        assert_eq!(editor.text(cx), "bcade\n");
 6107        assert_eq!(
 6108            editor.selections.ranges(&editor.display_snapshot(cx)),
 6109            [3..3, 4..4, 6..6]
 6110        );
 6111
 6112        editor.transpose(&Default::default(), window, cx);
 6113        assert_eq!(editor.text(cx), "bcda\ne");
 6114        assert_eq!(
 6115            editor.selections.ranges(&editor.display_snapshot(cx)),
 6116            [4..4, 6..6]
 6117        );
 6118
 6119        editor.transpose(&Default::default(), window, cx);
 6120        assert_eq!(editor.text(cx), "bcade\n");
 6121        assert_eq!(
 6122            editor.selections.ranges(&editor.display_snapshot(cx)),
 6123            [4..4, 6..6]
 6124        );
 6125
 6126        editor.transpose(&Default::default(), window, cx);
 6127        assert_eq!(editor.text(cx), "bcaed\n");
 6128        assert_eq!(
 6129            editor.selections.ranges(&editor.display_snapshot(cx)),
 6130            [5..5, 6..6]
 6131        );
 6132
 6133        editor
 6134    });
 6135
 6136    _ = cx.add_window(|window, cx| {
 6137        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 6138        editor.set_style(EditorStyle::default(), window, cx);
 6139        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6140            s.select_ranges([4..4])
 6141        });
 6142        editor.transpose(&Default::default(), window, cx);
 6143        assert_eq!(editor.text(cx), "🏀🍐✋");
 6144        assert_eq!(
 6145            editor.selections.ranges(&editor.display_snapshot(cx)),
 6146            [8..8]
 6147        );
 6148
 6149        editor.transpose(&Default::default(), window, cx);
 6150        assert_eq!(editor.text(cx), "🏀✋🍐");
 6151        assert_eq!(
 6152            editor.selections.ranges(&editor.display_snapshot(cx)),
 6153            [11..11]
 6154        );
 6155
 6156        editor.transpose(&Default::default(), window, cx);
 6157        assert_eq!(editor.text(cx), "🏀🍐✋");
 6158        assert_eq!(
 6159            editor.selections.ranges(&editor.display_snapshot(cx)),
 6160            [11..11]
 6161        );
 6162
 6163        editor
 6164    });
 6165}
 6166
 6167#[gpui::test]
 6168async fn test_rewrap(cx: &mut TestAppContext) {
 6169    init_test(cx, |settings| {
 6170        settings.languages.0.extend([
 6171            (
 6172                "Markdown".into(),
 6173                LanguageSettingsContent {
 6174                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 6175                    preferred_line_length: Some(40),
 6176                    ..Default::default()
 6177                },
 6178            ),
 6179            (
 6180                "Plain Text".into(),
 6181                LanguageSettingsContent {
 6182                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 6183                    preferred_line_length: Some(40),
 6184                    ..Default::default()
 6185                },
 6186            ),
 6187            (
 6188                "C++".into(),
 6189                LanguageSettingsContent {
 6190                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6191                    preferred_line_length: Some(40),
 6192                    ..Default::default()
 6193                },
 6194            ),
 6195            (
 6196                "Python".into(),
 6197                LanguageSettingsContent {
 6198                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6199                    preferred_line_length: Some(40),
 6200                    ..Default::default()
 6201                },
 6202            ),
 6203            (
 6204                "Rust".into(),
 6205                LanguageSettingsContent {
 6206                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6207                    preferred_line_length: Some(40),
 6208                    ..Default::default()
 6209                },
 6210            ),
 6211        ])
 6212    });
 6213
 6214    let mut cx = EditorTestContext::new(cx).await;
 6215
 6216    let cpp_language = Arc::new(Language::new(
 6217        LanguageConfig {
 6218            name: "C++".into(),
 6219            line_comments: vec!["// ".into()],
 6220            ..LanguageConfig::default()
 6221        },
 6222        None,
 6223    ));
 6224    let python_language = Arc::new(Language::new(
 6225        LanguageConfig {
 6226            name: "Python".into(),
 6227            line_comments: vec!["# ".into()],
 6228            ..LanguageConfig::default()
 6229        },
 6230        None,
 6231    ));
 6232    let markdown_language = Arc::new(Language::new(
 6233        LanguageConfig {
 6234            name: "Markdown".into(),
 6235            rewrap_prefixes: vec![
 6236                regex::Regex::new("\\d+\\.\\s+").unwrap(),
 6237                regex::Regex::new("[-*+]\\s+").unwrap(),
 6238            ],
 6239            ..LanguageConfig::default()
 6240        },
 6241        None,
 6242    ));
 6243    let rust_language = Arc::new(
 6244        Language::new(
 6245            LanguageConfig {
 6246                name: "Rust".into(),
 6247                line_comments: vec!["// ".into(), "/// ".into()],
 6248                ..LanguageConfig::default()
 6249            },
 6250            Some(tree_sitter_rust::LANGUAGE.into()),
 6251        )
 6252        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 6253        .unwrap(),
 6254    );
 6255
 6256    let plaintext_language = Arc::new(Language::new(
 6257        LanguageConfig {
 6258            name: "Plain Text".into(),
 6259            ..LanguageConfig::default()
 6260        },
 6261        None,
 6262    ));
 6263
 6264    // Test basic rewrapping of a long line with a cursor
 6265    assert_rewrap(
 6266        indoc! {"
 6267            // ˇThis is a long comment that needs to be wrapped.
 6268        "},
 6269        indoc! {"
 6270            // ˇThis is a long comment that needs to
 6271            // be wrapped.
 6272        "},
 6273        cpp_language.clone(),
 6274        &mut cx,
 6275    );
 6276
 6277    // Test rewrapping a full selection
 6278    assert_rewrap(
 6279        indoc! {"
 6280            «// This selected long comment needs to be wrapped.ˇ»"
 6281        },
 6282        indoc! {"
 6283            «// This selected long comment needs to
 6284            // be wrapped.ˇ»"
 6285        },
 6286        cpp_language.clone(),
 6287        &mut cx,
 6288    );
 6289
 6290    // Test multiple cursors on different lines within the same paragraph are preserved after rewrapping
 6291    assert_rewrap(
 6292        indoc! {"
 6293            // ˇThis is the first line.
 6294            // Thisˇ is the second line.
 6295            // This is the thirdˇ line, all part of one paragraph.
 6296         "},
 6297        indoc! {"
 6298            // ˇThis is the first line. Thisˇ is the
 6299            // second line. This is the thirdˇ line,
 6300            // all part of one paragraph.
 6301         "},
 6302        cpp_language.clone(),
 6303        &mut cx,
 6304    );
 6305
 6306    // Test multiple cursors in different paragraphs trigger separate rewraps
 6307    assert_rewrap(
 6308        indoc! {"
 6309            // ˇThis is the first paragraph, first line.
 6310            // ˇThis is the first paragraph, second line.
 6311
 6312            // ˇThis is the second paragraph, first line.
 6313            // ˇThis is the second paragraph, second line.
 6314        "},
 6315        indoc! {"
 6316            // ˇThis is the first paragraph, first
 6317            // line. ˇThis is the first paragraph,
 6318            // second line.
 6319
 6320            // ˇThis is the second paragraph, first
 6321            // line. ˇThis is the second paragraph,
 6322            // second line.
 6323        "},
 6324        cpp_language.clone(),
 6325        &mut cx,
 6326    );
 6327
 6328    // Test that change in comment prefix (e.g., `//` to `///`) trigger seperate rewraps
 6329    assert_rewrap(
 6330        indoc! {"
 6331            «// A regular long long comment to be wrapped.
 6332            /// A documentation long comment to be wrapped.ˇ»
 6333          "},
 6334        indoc! {"
 6335            «// A regular long long comment to be
 6336            // wrapped.
 6337            /// A documentation long comment to be
 6338            /// wrapped.ˇ»
 6339          "},
 6340        rust_language.clone(),
 6341        &mut cx,
 6342    );
 6343
 6344    // Test that change in indentation level trigger seperate rewraps
 6345    assert_rewrap(
 6346        indoc! {"
 6347            fn foo() {
 6348                «// This is a long comment at the base indent.
 6349                    // This is a long comment at the next indent.ˇ»
 6350            }
 6351        "},
 6352        indoc! {"
 6353            fn foo() {
 6354                «// This is a long comment at the
 6355                // base indent.
 6356                    // This is a long comment at the
 6357                    // next indent.ˇ»
 6358            }
 6359        "},
 6360        rust_language.clone(),
 6361        &mut cx,
 6362    );
 6363
 6364    // Test that different comment prefix characters (e.g., '#') are handled correctly
 6365    assert_rewrap(
 6366        indoc! {"
 6367            # ˇThis is a long comment using a pound sign.
 6368        "},
 6369        indoc! {"
 6370            # ˇThis is a long comment using a pound
 6371            # sign.
 6372        "},
 6373        python_language,
 6374        &mut cx,
 6375    );
 6376
 6377    // Test rewrapping only affects comments, not code even when selected
 6378    assert_rewrap(
 6379        indoc! {"
 6380            «/// This doc comment is long and should be wrapped.
 6381            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6382        "},
 6383        indoc! {"
 6384            «/// This doc comment is long and should
 6385            /// be wrapped.
 6386            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6387        "},
 6388        rust_language.clone(),
 6389        &mut cx,
 6390    );
 6391
 6392    // Test that rewrapping works in Markdown documents where `allow_rewrap` is `Anywhere`
 6393    assert_rewrap(
 6394        indoc! {"
 6395            # Header
 6396
 6397            A long long long line of markdown text to wrap.ˇ
 6398         "},
 6399        indoc! {"
 6400            # Header
 6401
 6402            A long long long line of markdown text
 6403            to wrap.ˇ
 6404         "},
 6405        markdown_language.clone(),
 6406        &mut cx,
 6407    );
 6408
 6409    // Test that rewrapping boundary works and preserves relative indent for Markdown documents
 6410    assert_rewrap(
 6411        indoc! {"
 6412            «1. This is a numbered list item that is very long and needs to be wrapped properly.
 6413            2. This is a numbered list item that is very long and needs to be wrapped properly.
 6414            - This is an unordered list item that is also very long and should not merge with the numbered item.ˇ»
 6415        "},
 6416        indoc! {"
 6417            «1. This is a numbered list item that is
 6418               very long and needs to be wrapped
 6419               properly.
 6420            2. This is a numbered list item that is
 6421               very long and needs to be wrapped
 6422               properly.
 6423            - This is an unordered list item that is
 6424              also very long and should not merge
 6425              with the numbered item.ˇ»
 6426        "},
 6427        markdown_language.clone(),
 6428        &mut cx,
 6429    );
 6430
 6431    // Test that rewrapping add indents for rewrapping boundary if not exists already.
 6432    assert_rewrap(
 6433        indoc! {"
 6434            «1. This is a numbered list item that is
 6435            very long and needs to be wrapped
 6436            properly.
 6437            2. This is a numbered list item that is
 6438            very long and needs to be wrapped
 6439            properly.
 6440            - This is an unordered list item that is
 6441            also very long and should not merge with
 6442            the numbered item.ˇ»
 6443        "},
 6444        indoc! {"
 6445            «1. This is a numbered list item that is
 6446               very long and needs to be wrapped
 6447               properly.
 6448            2. This is a numbered list item that is
 6449               very long and needs to be wrapped
 6450               properly.
 6451            - This is an unordered list item that is
 6452              also very long and should not merge
 6453              with the numbered item.ˇ»
 6454        "},
 6455        markdown_language.clone(),
 6456        &mut cx,
 6457    );
 6458
 6459    // Test that rewrapping maintain indents even when they already exists.
 6460    assert_rewrap(
 6461        indoc! {"
 6462            «1. This is a numbered list
 6463               item that is very long and needs to be wrapped properly.
 6464            2. This is a numbered list
 6465               item that is very long and needs to be wrapped properly.
 6466            - This is an unordered list item that is also very long and
 6467              should not merge with the numbered item.ˇ»
 6468        "},
 6469        indoc! {"
 6470            «1. This is a numbered list item that is
 6471               very long and needs to be wrapped
 6472               properly.
 6473            2. This is a numbered list item that is
 6474               very long and needs to be wrapped
 6475               properly.
 6476            - This is an unordered list item that is
 6477              also very long and should not merge
 6478              with the numbered item.ˇ»
 6479        "},
 6480        markdown_language,
 6481        &mut cx,
 6482    );
 6483
 6484    // Test that rewrapping works in plain text where `allow_rewrap` is `Anywhere`
 6485    assert_rewrap(
 6486        indoc! {"
 6487            ˇThis is a very long line of plain text that will be wrapped.
 6488        "},
 6489        indoc! {"
 6490            ˇThis is a very long line of plain text
 6491            that will be wrapped.
 6492        "},
 6493        plaintext_language.clone(),
 6494        &mut cx,
 6495    );
 6496
 6497    // Test that non-commented code acts as a paragraph boundary within a selection
 6498    assert_rewrap(
 6499        indoc! {"
 6500               «// This is the first long comment block to be wrapped.
 6501               fn my_func(a: u32);
 6502               // This is the second long comment block to be wrapped.ˇ»
 6503           "},
 6504        indoc! {"
 6505               «// This is the first long comment block
 6506               // to be wrapped.
 6507               fn my_func(a: u32);
 6508               // This is the second long comment block
 6509               // to be wrapped.ˇ»
 6510           "},
 6511        rust_language,
 6512        &mut cx,
 6513    );
 6514
 6515    // Test rewrapping multiple selections, including ones with blank lines or tabs
 6516    assert_rewrap(
 6517        indoc! {"
 6518            «ˇThis is a very long line that will be wrapped.
 6519
 6520            This is another paragraph in the same selection.»
 6521
 6522            «\tThis is a very long indented line that will be wrapped.ˇ»
 6523         "},
 6524        indoc! {"
 6525            «ˇThis is a very long line that will be
 6526            wrapped.
 6527
 6528            This is another paragraph in the same
 6529            selection.»
 6530
 6531            «\tThis is a very long indented line
 6532            \tthat will be wrapped.ˇ»
 6533         "},
 6534        plaintext_language,
 6535        &mut cx,
 6536    );
 6537
 6538    // Test that an empty comment line acts as a paragraph boundary
 6539    assert_rewrap(
 6540        indoc! {"
 6541            // ˇThis is a long comment that will be wrapped.
 6542            //
 6543            // And this is another long comment that will also be wrapped.ˇ
 6544         "},
 6545        indoc! {"
 6546            // ˇThis is a long comment that will be
 6547            // wrapped.
 6548            //
 6549            // And this is another long comment that
 6550            // will also be wrapped.ˇ
 6551         "},
 6552        cpp_language,
 6553        &mut cx,
 6554    );
 6555
 6556    #[track_caller]
 6557    fn assert_rewrap(
 6558        unwrapped_text: &str,
 6559        wrapped_text: &str,
 6560        language: Arc<Language>,
 6561        cx: &mut EditorTestContext,
 6562    ) {
 6563        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6564        cx.set_state(unwrapped_text);
 6565        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6566        cx.assert_editor_state(wrapped_text);
 6567    }
 6568}
 6569
 6570#[gpui::test]
 6571async fn test_rewrap_block_comments(cx: &mut TestAppContext) {
 6572    init_test(cx, |settings| {
 6573        settings.languages.0.extend([(
 6574            "Rust".into(),
 6575            LanguageSettingsContent {
 6576                allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6577                preferred_line_length: Some(40),
 6578                ..Default::default()
 6579            },
 6580        )])
 6581    });
 6582
 6583    let mut cx = EditorTestContext::new(cx).await;
 6584
 6585    let rust_lang = Arc::new(
 6586        Language::new(
 6587            LanguageConfig {
 6588                name: "Rust".into(),
 6589                line_comments: vec!["// ".into()],
 6590                block_comment: Some(BlockCommentConfig {
 6591                    start: "/*".into(),
 6592                    end: "*/".into(),
 6593                    prefix: "* ".into(),
 6594                    tab_size: 1,
 6595                }),
 6596                documentation_comment: Some(BlockCommentConfig {
 6597                    start: "/**".into(),
 6598                    end: "*/".into(),
 6599                    prefix: "* ".into(),
 6600                    tab_size: 1,
 6601                }),
 6602
 6603                ..LanguageConfig::default()
 6604            },
 6605            Some(tree_sitter_rust::LANGUAGE.into()),
 6606        )
 6607        .with_override_query("[(line_comment) (block_comment)] @comment.inclusive")
 6608        .unwrap(),
 6609    );
 6610
 6611    // regular block comment
 6612    assert_rewrap(
 6613        indoc! {"
 6614            /*
 6615             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6616             */
 6617            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6618        "},
 6619        indoc! {"
 6620            /*
 6621             *ˇ Lorem ipsum dolor sit amet,
 6622             * consectetur adipiscing elit.
 6623             */
 6624            /*
 6625             *ˇ Lorem ipsum dolor sit amet,
 6626             * consectetur adipiscing elit.
 6627             */
 6628        "},
 6629        rust_lang.clone(),
 6630        &mut cx,
 6631    );
 6632
 6633    // indent is respected
 6634    assert_rewrap(
 6635        indoc! {"
 6636            {}
 6637                /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6638        "},
 6639        indoc! {"
 6640            {}
 6641                /*
 6642                 *ˇ Lorem ipsum dolor sit amet,
 6643                 * consectetur adipiscing elit.
 6644                 */
 6645        "},
 6646        rust_lang.clone(),
 6647        &mut cx,
 6648    );
 6649
 6650    // short block comments with inline delimiters
 6651    assert_rewrap(
 6652        indoc! {"
 6653            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6654            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6655             */
 6656            /*
 6657             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6658        "},
 6659        indoc! {"
 6660            /*
 6661             *ˇ Lorem ipsum dolor sit amet,
 6662             * consectetur adipiscing elit.
 6663             */
 6664            /*
 6665             *ˇ Lorem ipsum dolor sit amet,
 6666             * consectetur adipiscing elit.
 6667             */
 6668            /*
 6669             *ˇ Lorem ipsum dolor sit amet,
 6670             * consectetur adipiscing elit.
 6671             */
 6672        "},
 6673        rust_lang.clone(),
 6674        &mut cx,
 6675    );
 6676
 6677    // multiline block comment with inline start/end delimiters
 6678    assert_rewrap(
 6679        indoc! {"
 6680            /*ˇ Lorem ipsum dolor sit amet,
 6681             * consectetur adipiscing elit. */
 6682        "},
 6683        indoc! {"
 6684            /*
 6685             *ˇ Lorem ipsum dolor sit amet,
 6686             * consectetur adipiscing elit.
 6687             */
 6688        "},
 6689        rust_lang.clone(),
 6690        &mut cx,
 6691    );
 6692
 6693    // block comment rewrap still respects paragraph bounds
 6694    assert_rewrap(
 6695        indoc! {"
 6696            /*
 6697             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6698             *
 6699             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6700             */
 6701        "},
 6702        indoc! {"
 6703            /*
 6704             *ˇ Lorem ipsum dolor sit amet,
 6705             * consectetur adipiscing elit.
 6706             *
 6707             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6708             */
 6709        "},
 6710        rust_lang.clone(),
 6711        &mut cx,
 6712    );
 6713
 6714    // documentation comments
 6715    assert_rewrap(
 6716        indoc! {"
 6717            /**ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6718            /**
 6719             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6720             */
 6721        "},
 6722        indoc! {"
 6723            /**
 6724             *ˇ Lorem ipsum dolor sit amet,
 6725             * consectetur adipiscing elit.
 6726             */
 6727            /**
 6728             *ˇ Lorem ipsum dolor sit amet,
 6729             * consectetur adipiscing elit.
 6730             */
 6731        "},
 6732        rust_lang.clone(),
 6733        &mut cx,
 6734    );
 6735
 6736    // different, adjacent comments
 6737    assert_rewrap(
 6738        indoc! {"
 6739            /**
 6740             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6741             */
 6742            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6743            //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6744        "},
 6745        indoc! {"
 6746            /**
 6747             *ˇ Lorem ipsum dolor sit amet,
 6748             * consectetur adipiscing elit.
 6749             */
 6750            /*
 6751             *ˇ Lorem ipsum dolor sit amet,
 6752             * consectetur adipiscing elit.
 6753             */
 6754            //ˇ Lorem ipsum dolor sit amet,
 6755            // consectetur adipiscing elit.
 6756        "},
 6757        rust_lang.clone(),
 6758        &mut cx,
 6759    );
 6760
 6761    // selection w/ single short block comment
 6762    assert_rewrap(
 6763        indoc! {"
 6764            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6765        "},
 6766        indoc! {"
 6767            «/*
 6768             * Lorem ipsum dolor sit amet,
 6769             * consectetur adipiscing elit.
 6770             */ˇ»
 6771        "},
 6772        rust_lang.clone(),
 6773        &mut cx,
 6774    );
 6775
 6776    // rewrapping a single comment w/ abutting comments
 6777    assert_rewrap(
 6778        indoc! {"
 6779            /* ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6780            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6781        "},
 6782        indoc! {"
 6783            /*
 6784             * ˇLorem ipsum dolor sit amet,
 6785             * consectetur adipiscing elit.
 6786             */
 6787            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6788        "},
 6789        rust_lang.clone(),
 6790        &mut cx,
 6791    );
 6792
 6793    // selection w/ non-abutting short block comments
 6794    assert_rewrap(
 6795        indoc! {"
 6796            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6797
 6798            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6799        "},
 6800        indoc! {"
 6801            «/*
 6802             * Lorem ipsum dolor sit amet,
 6803             * consectetur adipiscing elit.
 6804             */
 6805
 6806            /*
 6807             * Lorem ipsum dolor sit amet,
 6808             * consectetur adipiscing elit.
 6809             */ˇ»
 6810        "},
 6811        rust_lang.clone(),
 6812        &mut cx,
 6813    );
 6814
 6815    // selection of multiline block comments
 6816    assert_rewrap(
 6817        indoc! {"
 6818            «/* Lorem ipsum dolor sit amet,
 6819             * consectetur adipiscing elit. */ˇ»
 6820        "},
 6821        indoc! {"
 6822            «/*
 6823             * Lorem ipsum dolor sit amet,
 6824             * consectetur adipiscing elit.
 6825             */ˇ»
 6826        "},
 6827        rust_lang.clone(),
 6828        &mut cx,
 6829    );
 6830
 6831    // partial selection of multiline block comments
 6832    assert_rewrap(
 6833        indoc! {"
 6834            «/* Lorem ipsum dolor sit amet,ˇ»
 6835             * consectetur adipiscing elit. */
 6836            /* Lorem ipsum dolor sit amet,
 6837             «* consectetur adipiscing elit. */ˇ»
 6838        "},
 6839        indoc! {"
 6840            «/*
 6841             * Lorem ipsum dolor sit amet,ˇ»
 6842             * consectetur adipiscing elit. */
 6843            /* Lorem ipsum dolor sit amet,
 6844             «* consectetur adipiscing elit.
 6845             */ˇ»
 6846        "},
 6847        rust_lang.clone(),
 6848        &mut cx,
 6849    );
 6850
 6851    // selection w/ abutting short block comments
 6852    // TODO: should not be combined; should rewrap as 2 comments
 6853    assert_rewrap(
 6854        indoc! {"
 6855            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6856            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6857        "},
 6858        // desired behavior:
 6859        // indoc! {"
 6860        //     «/*
 6861        //      * Lorem ipsum dolor sit amet,
 6862        //      * consectetur adipiscing elit.
 6863        //      */
 6864        //     /*
 6865        //      * Lorem ipsum dolor sit amet,
 6866        //      * consectetur adipiscing elit.
 6867        //      */ˇ»
 6868        // "},
 6869        // actual behaviour:
 6870        indoc! {"
 6871            «/*
 6872             * Lorem ipsum dolor sit amet,
 6873             * consectetur adipiscing elit. Lorem
 6874             * ipsum dolor sit amet, consectetur
 6875             * adipiscing elit.
 6876             */ˇ»
 6877        "},
 6878        rust_lang.clone(),
 6879        &mut cx,
 6880    );
 6881
 6882    // TODO: same as above, but with delimiters on separate line
 6883    // assert_rewrap(
 6884    //     indoc! {"
 6885    //         «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6886    //          */
 6887    //         /*
 6888    //          * Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6889    //     "},
 6890    //     // desired:
 6891    //     // indoc! {"
 6892    //     //     «/*
 6893    //     //      * Lorem ipsum dolor sit amet,
 6894    //     //      * consectetur adipiscing elit.
 6895    //     //      */
 6896    //     //     /*
 6897    //     //      * Lorem ipsum dolor sit amet,
 6898    //     //      * consectetur adipiscing elit.
 6899    //     //      */ˇ»
 6900    //     // "},
 6901    //     // actual: (but with trailing w/s on the empty lines)
 6902    //     indoc! {"
 6903    //         «/*
 6904    //          * Lorem ipsum dolor sit amet,
 6905    //          * consectetur adipiscing elit.
 6906    //          *
 6907    //          */
 6908    //         /*
 6909    //          *
 6910    //          * Lorem ipsum dolor sit amet,
 6911    //          * consectetur adipiscing elit.
 6912    //          */ˇ»
 6913    //     "},
 6914    //     rust_lang.clone(),
 6915    //     &mut cx,
 6916    // );
 6917
 6918    // TODO these are unhandled edge cases; not correct, just documenting known issues
 6919    assert_rewrap(
 6920        indoc! {"
 6921            /*
 6922             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6923             */
 6924            /*
 6925             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6926            /*ˇ Lorem ipsum dolor sit amet */ /* consectetur adipiscing elit. */
 6927        "},
 6928        // desired:
 6929        // indoc! {"
 6930        //     /*
 6931        //      *ˇ Lorem ipsum dolor sit amet,
 6932        //      * consectetur adipiscing elit.
 6933        //      */
 6934        //     /*
 6935        //      *ˇ Lorem ipsum dolor sit amet,
 6936        //      * consectetur adipiscing elit.
 6937        //      */
 6938        //     /*
 6939        //      *ˇ Lorem ipsum dolor sit amet
 6940        //      */ /* consectetur adipiscing elit. */
 6941        // "},
 6942        // actual:
 6943        indoc! {"
 6944            /*
 6945             //ˇ Lorem ipsum dolor sit amet,
 6946             // consectetur adipiscing elit.
 6947             */
 6948            /*
 6949             * //ˇ Lorem ipsum dolor sit amet,
 6950             * consectetur adipiscing elit.
 6951             */
 6952            /*
 6953             *ˇ Lorem ipsum dolor sit amet */ /*
 6954             * consectetur adipiscing elit.
 6955             */
 6956        "},
 6957        rust_lang,
 6958        &mut cx,
 6959    );
 6960
 6961    #[track_caller]
 6962    fn assert_rewrap(
 6963        unwrapped_text: &str,
 6964        wrapped_text: &str,
 6965        language: Arc<Language>,
 6966        cx: &mut EditorTestContext,
 6967    ) {
 6968        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6969        cx.set_state(unwrapped_text);
 6970        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6971        cx.assert_editor_state(wrapped_text);
 6972    }
 6973}
 6974
 6975#[gpui::test]
 6976async fn test_hard_wrap(cx: &mut TestAppContext) {
 6977    init_test(cx, |_| {});
 6978    let mut cx = EditorTestContext::new(cx).await;
 6979
 6980    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 6981    cx.update_editor(|editor, _, cx| {
 6982        editor.set_hard_wrap(Some(14), cx);
 6983    });
 6984
 6985    cx.set_state(indoc!(
 6986        "
 6987        one two three ˇ
 6988        "
 6989    ));
 6990    cx.simulate_input("four");
 6991    cx.run_until_parked();
 6992
 6993    cx.assert_editor_state(indoc!(
 6994        "
 6995        one two three
 6996        fourˇ
 6997        "
 6998    ));
 6999
 7000    cx.update_editor(|editor, window, cx| {
 7001        editor.newline(&Default::default(), window, cx);
 7002    });
 7003    cx.run_until_parked();
 7004    cx.assert_editor_state(indoc!(
 7005        "
 7006        one two three
 7007        four
 7008        ˇ
 7009        "
 7010    ));
 7011
 7012    cx.simulate_input("five");
 7013    cx.run_until_parked();
 7014    cx.assert_editor_state(indoc!(
 7015        "
 7016        one two three
 7017        four
 7018        fiveˇ
 7019        "
 7020    ));
 7021
 7022    cx.update_editor(|editor, window, cx| {
 7023        editor.newline(&Default::default(), window, cx);
 7024    });
 7025    cx.run_until_parked();
 7026    cx.simulate_input("# ");
 7027    cx.run_until_parked();
 7028    cx.assert_editor_state(indoc!(
 7029        "
 7030        one two three
 7031        four
 7032        five
 7033        # ˇ
 7034        "
 7035    ));
 7036
 7037    cx.update_editor(|editor, window, cx| {
 7038        editor.newline(&Default::default(), window, cx);
 7039    });
 7040    cx.run_until_parked();
 7041    cx.assert_editor_state(indoc!(
 7042        "
 7043        one two three
 7044        four
 7045        five
 7046        #\x20
 7047 7048        "
 7049    ));
 7050
 7051    cx.simulate_input(" 6");
 7052    cx.run_until_parked();
 7053    cx.assert_editor_state(indoc!(
 7054        "
 7055        one two three
 7056        four
 7057        five
 7058        #
 7059        # 6ˇ
 7060        "
 7061    ));
 7062}
 7063
 7064#[gpui::test]
 7065async fn test_cut_line_ends(cx: &mut TestAppContext) {
 7066    init_test(cx, |_| {});
 7067
 7068    let mut cx = EditorTestContext::new(cx).await;
 7069
 7070    cx.set_state(indoc! {"The quick brownˇ"});
 7071    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 7072    cx.assert_editor_state(indoc! {"The quick brownˇ"});
 7073
 7074    cx.set_state(indoc! {"The emacs foxˇ"});
 7075    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 7076    cx.assert_editor_state(indoc! {"The emacs foxˇ"});
 7077
 7078    cx.set_state(indoc! {"
 7079        The quick« brownˇ»
 7080        fox jumps overˇ
 7081        the lazy dog"});
 7082    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7083    cx.assert_editor_state(indoc! {"
 7084        The quickˇ
 7085        ˇthe lazy dog"});
 7086
 7087    cx.set_state(indoc! {"
 7088        The quick« brownˇ»
 7089        fox jumps overˇ
 7090        the lazy dog"});
 7091    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 7092    cx.assert_editor_state(indoc! {"
 7093        The quickˇ
 7094        fox jumps overˇthe lazy dog"});
 7095
 7096    cx.set_state(indoc! {"
 7097        The quick« brownˇ»
 7098        fox jumps overˇ
 7099        the lazy dog"});
 7100    cx.update_editor(|e, window, cx| {
 7101        e.cut_to_end_of_line(
 7102            &CutToEndOfLine {
 7103                stop_at_newlines: true,
 7104            },
 7105            window,
 7106            cx,
 7107        )
 7108    });
 7109    cx.assert_editor_state(indoc! {"
 7110        The quickˇ
 7111        fox jumps overˇ
 7112        the lazy dog"});
 7113
 7114    cx.set_state(indoc! {"
 7115        The quick« brownˇ»
 7116        fox jumps overˇ
 7117        the lazy dog"});
 7118    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 7119    cx.assert_editor_state(indoc! {"
 7120        The quickˇ
 7121        fox jumps overˇthe lazy dog"});
 7122}
 7123
 7124#[gpui::test]
 7125async fn test_clipboard(cx: &mut TestAppContext) {
 7126    init_test(cx, |_| {});
 7127
 7128    let mut cx = EditorTestContext::new(cx).await;
 7129
 7130    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 7131    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7132    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 7133
 7134    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 7135    cx.set_state("two ˇfour ˇsix ˇ");
 7136    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7137    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 7138
 7139    // Paste again but with only two cursors. Since the number of cursors doesn't
 7140    // match the number of slices in the clipboard, the entire clipboard text
 7141    // is pasted at each cursor.
 7142    cx.set_state("ˇtwo one✅ four three six five ˇ");
 7143    cx.update_editor(|e, window, cx| {
 7144        e.handle_input("( ", window, cx);
 7145        e.paste(&Paste, window, cx);
 7146        e.handle_input(") ", window, cx);
 7147    });
 7148    cx.assert_editor_state(
 7149        &([
 7150            "( one✅ ",
 7151            "three ",
 7152            "five ) ˇtwo one✅ four three six five ( one✅ ",
 7153            "three ",
 7154            "five ) ˇ",
 7155        ]
 7156        .join("\n")),
 7157    );
 7158
 7159    // Cut with three selections, one of which is full-line.
 7160    cx.set_state(indoc! {"
 7161        1«2ˇ»3
 7162        4ˇ567
 7163        «8ˇ»9"});
 7164    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7165    cx.assert_editor_state(indoc! {"
 7166        1ˇ3
 7167        ˇ9"});
 7168
 7169    // Paste with three selections, noticing how the copied selection that was full-line
 7170    // gets inserted before the second cursor.
 7171    cx.set_state(indoc! {"
 7172        1ˇ3
 7173 7174        «oˇ»ne"});
 7175    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7176    cx.assert_editor_state(indoc! {"
 7177        12ˇ3
 7178        4567
 7179 7180        8ˇne"});
 7181
 7182    // Copy with a single cursor only, which writes the whole line into the clipboard.
 7183    cx.set_state(indoc! {"
 7184        The quick brown
 7185        fox juˇmps over
 7186        the lazy dog"});
 7187    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7188    assert_eq!(
 7189        cx.read_from_clipboard()
 7190            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7191        Some("fox jumps over\n".to_string())
 7192    );
 7193
 7194    // Paste with three selections, noticing how the copied full-line selection is inserted
 7195    // before the empty selections but replaces the selection that is non-empty.
 7196    cx.set_state(indoc! {"
 7197        Tˇhe quick brown
 7198        «foˇ»x jumps over
 7199        tˇhe lazy dog"});
 7200    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7201    cx.assert_editor_state(indoc! {"
 7202        fox jumps over
 7203        Tˇhe quick brown
 7204        fox jumps over
 7205        ˇx jumps over
 7206        fox jumps over
 7207        tˇhe lazy dog"});
 7208}
 7209
 7210#[gpui::test]
 7211async fn test_copy_trim(cx: &mut TestAppContext) {
 7212    init_test(cx, |_| {});
 7213
 7214    let mut cx = EditorTestContext::new(cx).await;
 7215    cx.set_state(
 7216        r#"            «for selection in selections.iter() {
 7217            let mut start = selection.start;
 7218            let mut end = selection.end;
 7219            let is_entire_line = selection.is_empty();
 7220            if is_entire_line {
 7221                start = Point::new(start.row, 0);ˇ»
 7222                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7223            }
 7224        "#,
 7225    );
 7226    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7227    assert_eq!(
 7228        cx.read_from_clipboard()
 7229            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7230        Some(
 7231            "for selection in selections.iter() {
 7232            let mut start = selection.start;
 7233            let mut end = selection.end;
 7234            let is_entire_line = selection.is_empty();
 7235            if is_entire_line {
 7236                start = Point::new(start.row, 0);"
 7237                .to_string()
 7238        ),
 7239        "Regular copying preserves all indentation selected",
 7240    );
 7241    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7242    assert_eq!(
 7243        cx.read_from_clipboard()
 7244            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7245        Some(
 7246            "for selection in selections.iter() {
 7247let mut start = selection.start;
 7248let mut end = selection.end;
 7249let is_entire_line = selection.is_empty();
 7250if is_entire_line {
 7251    start = Point::new(start.row, 0);"
 7252                .to_string()
 7253        ),
 7254        "Copying with stripping should strip all leading whitespaces"
 7255    );
 7256
 7257    cx.set_state(
 7258        r#"       «     for selection in selections.iter() {
 7259            let mut start = selection.start;
 7260            let mut end = selection.end;
 7261            let is_entire_line = selection.is_empty();
 7262            if is_entire_line {
 7263                start = Point::new(start.row, 0);ˇ»
 7264                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7265            }
 7266        "#,
 7267    );
 7268    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7269    assert_eq!(
 7270        cx.read_from_clipboard()
 7271            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7272        Some(
 7273            "     for selection in selections.iter() {
 7274            let mut start = selection.start;
 7275            let mut end = selection.end;
 7276            let is_entire_line = selection.is_empty();
 7277            if is_entire_line {
 7278                start = Point::new(start.row, 0);"
 7279                .to_string()
 7280        ),
 7281        "Regular copying preserves all indentation selected",
 7282    );
 7283    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7284    assert_eq!(
 7285        cx.read_from_clipboard()
 7286            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7287        Some(
 7288            "for selection in selections.iter() {
 7289let mut start = selection.start;
 7290let mut end = selection.end;
 7291let is_entire_line = selection.is_empty();
 7292if is_entire_line {
 7293    start = Point::new(start.row, 0);"
 7294                .to_string()
 7295        ),
 7296        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 7297    );
 7298
 7299    cx.set_state(
 7300        r#"       «ˇ     for selection in selections.iter() {
 7301            let mut start = selection.start;
 7302            let mut end = selection.end;
 7303            let is_entire_line = selection.is_empty();
 7304            if is_entire_line {
 7305                start = Point::new(start.row, 0);»
 7306                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7307            }
 7308        "#,
 7309    );
 7310    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7311    assert_eq!(
 7312        cx.read_from_clipboard()
 7313            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7314        Some(
 7315            "     for selection in selections.iter() {
 7316            let mut start = selection.start;
 7317            let mut end = selection.end;
 7318            let is_entire_line = selection.is_empty();
 7319            if is_entire_line {
 7320                start = Point::new(start.row, 0);"
 7321                .to_string()
 7322        ),
 7323        "Regular copying for reverse selection works the same",
 7324    );
 7325    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7326    assert_eq!(
 7327        cx.read_from_clipboard()
 7328            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7329        Some(
 7330            "for selection in selections.iter() {
 7331let mut start = selection.start;
 7332let mut end = selection.end;
 7333let is_entire_line = selection.is_empty();
 7334if is_entire_line {
 7335    start = Point::new(start.row, 0);"
 7336                .to_string()
 7337        ),
 7338        "Copying with stripping for reverse selection works the same"
 7339    );
 7340
 7341    cx.set_state(
 7342        r#"            for selection «in selections.iter() {
 7343            let mut start = selection.start;
 7344            let mut end = selection.end;
 7345            let is_entire_line = selection.is_empty();
 7346            if is_entire_line {
 7347                start = Point::new(start.row, 0);ˇ»
 7348                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7349            }
 7350        "#,
 7351    );
 7352    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7353    assert_eq!(
 7354        cx.read_from_clipboard()
 7355            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7356        Some(
 7357            "in selections.iter() {
 7358            let mut start = selection.start;
 7359            let mut end = selection.end;
 7360            let is_entire_line = selection.is_empty();
 7361            if is_entire_line {
 7362                start = Point::new(start.row, 0);"
 7363                .to_string()
 7364        ),
 7365        "When selecting past the indent, the copying works as usual",
 7366    );
 7367    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7368    assert_eq!(
 7369        cx.read_from_clipboard()
 7370            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7371        Some(
 7372            "in selections.iter() {
 7373            let mut start = selection.start;
 7374            let mut end = selection.end;
 7375            let is_entire_line = selection.is_empty();
 7376            if is_entire_line {
 7377                start = Point::new(start.row, 0);"
 7378                .to_string()
 7379        ),
 7380        "When selecting past the indent, nothing is trimmed"
 7381    );
 7382
 7383    cx.set_state(
 7384        r#"            «for selection in selections.iter() {
 7385            let mut start = selection.start;
 7386
 7387            let mut end = selection.end;
 7388            let is_entire_line = selection.is_empty();
 7389            if is_entire_line {
 7390                start = Point::new(start.row, 0);
 7391ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7392            }
 7393        "#,
 7394    );
 7395    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7396    assert_eq!(
 7397        cx.read_from_clipboard()
 7398            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7399        Some(
 7400            "for selection in selections.iter() {
 7401let mut start = selection.start;
 7402
 7403let mut end = selection.end;
 7404let is_entire_line = selection.is_empty();
 7405if is_entire_line {
 7406    start = Point::new(start.row, 0);
 7407"
 7408            .to_string()
 7409        ),
 7410        "Copying with stripping should ignore empty lines"
 7411    );
 7412}
 7413
 7414#[gpui::test]
 7415async fn test_paste_multiline(cx: &mut TestAppContext) {
 7416    init_test(cx, |_| {});
 7417
 7418    let mut cx = EditorTestContext::new(cx).await;
 7419    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7420
 7421    // Cut an indented block, without the leading whitespace.
 7422    cx.set_state(indoc! {"
 7423        const a: B = (
 7424            c(),
 7425            «d(
 7426                e,
 7427                f
 7428            )ˇ»
 7429        );
 7430    "});
 7431    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7432    cx.assert_editor_state(indoc! {"
 7433        const a: B = (
 7434            c(),
 7435            ˇ
 7436        );
 7437    "});
 7438
 7439    // Paste it at the same position.
 7440    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7441    cx.assert_editor_state(indoc! {"
 7442        const a: B = (
 7443            c(),
 7444            d(
 7445                e,
 7446                f
 7447 7448        );
 7449    "});
 7450
 7451    // Paste it at a line with a lower indent level.
 7452    cx.set_state(indoc! {"
 7453        ˇ
 7454        const a: B = (
 7455            c(),
 7456        );
 7457    "});
 7458    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7459    cx.assert_editor_state(indoc! {"
 7460        d(
 7461            e,
 7462            f
 7463 7464        const a: B = (
 7465            c(),
 7466        );
 7467    "});
 7468
 7469    // Cut an indented block, with the leading whitespace.
 7470    cx.set_state(indoc! {"
 7471        const a: B = (
 7472            c(),
 7473        «    d(
 7474                e,
 7475                f
 7476            )
 7477        ˇ»);
 7478    "});
 7479    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7480    cx.assert_editor_state(indoc! {"
 7481        const a: B = (
 7482            c(),
 7483        ˇ);
 7484    "});
 7485
 7486    // Paste it at the same position.
 7487    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7488    cx.assert_editor_state(indoc! {"
 7489        const a: B = (
 7490            c(),
 7491            d(
 7492                e,
 7493                f
 7494            )
 7495        ˇ);
 7496    "});
 7497
 7498    // Paste it at a line with a higher indent level.
 7499    cx.set_state(indoc! {"
 7500        const a: B = (
 7501            c(),
 7502            d(
 7503                e,
 7504 7505            )
 7506        );
 7507    "});
 7508    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7509    cx.assert_editor_state(indoc! {"
 7510        const a: B = (
 7511            c(),
 7512            d(
 7513                e,
 7514                f    d(
 7515                    e,
 7516                    f
 7517                )
 7518        ˇ
 7519            )
 7520        );
 7521    "});
 7522
 7523    // Copy an indented block, starting mid-line
 7524    cx.set_state(indoc! {"
 7525        const a: B = (
 7526            c(),
 7527            somethin«g(
 7528                e,
 7529                f
 7530            )ˇ»
 7531        );
 7532    "});
 7533    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7534
 7535    // Paste it on a line with a lower indent level
 7536    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 7537    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7538    cx.assert_editor_state(indoc! {"
 7539        const a: B = (
 7540            c(),
 7541            something(
 7542                e,
 7543                f
 7544            )
 7545        );
 7546        g(
 7547            e,
 7548            f
 7549"});
 7550}
 7551
 7552#[gpui::test]
 7553async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 7554    init_test(cx, |_| {});
 7555
 7556    cx.write_to_clipboard(ClipboardItem::new_string(
 7557        "    d(\n        e\n    );\n".into(),
 7558    ));
 7559
 7560    let mut cx = EditorTestContext::new(cx).await;
 7561    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7562
 7563    cx.set_state(indoc! {"
 7564        fn a() {
 7565            b();
 7566            if c() {
 7567                ˇ
 7568            }
 7569        }
 7570    "});
 7571
 7572    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7573    cx.assert_editor_state(indoc! {"
 7574        fn a() {
 7575            b();
 7576            if c() {
 7577                d(
 7578                    e
 7579                );
 7580        ˇ
 7581            }
 7582        }
 7583    "});
 7584
 7585    cx.set_state(indoc! {"
 7586        fn a() {
 7587            b();
 7588            ˇ
 7589        }
 7590    "});
 7591
 7592    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7593    cx.assert_editor_state(indoc! {"
 7594        fn a() {
 7595            b();
 7596            d(
 7597                e
 7598            );
 7599        ˇ
 7600        }
 7601    "});
 7602}
 7603
 7604#[gpui::test]
 7605fn test_select_all(cx: &mut TestAppContext) {
 7606    init_test(cx, |_| {});
 7607
 7608    let editor = cx.add_window(|window, cx| {
 7609        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 7610        build_editor(buffer, window, cx)
 7611    });
 7612    _ = editor.update(cx, |editor, window, cx| {
 7613        editor.select_all(&SelectAll, window, cx);
 7614        assert_eq!(
 7615            editor.selections.display_ranges(cx),
 7616            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 7617        );
 7618    });
 7619}
 7620
 7621#[gpui::test]
 7622fn test_select_line(cx: &mut TestAppContext) {
 7623    init_test(cx, |_| {});
 7624
 7625    let editor = cx.add_window(|window, cx| {
 7626        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 7627        build_editor(buffer, window, cx)
 7628    });
 7629    _ = editor.update(cx, |editor, window, cx| {
 7630        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7631            s.select_display_ranges([
 7632                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7633                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7634                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7635                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 7636            ])
 7637        });
 7638        editor.select_line(&SelectLine, window, cx);
 7639        assert_eq!(
 7640            editor.selections.display_ranges(cx),
 7641            vec![
 7642                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 7643                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 7644            ]
 7645        );
 7646    });
 7647
 7648    _ = editor.update(cx, |editor, window, cx| {
 7649        editor.select_line(&SelectLine, window, cx);
 7650        assert_eq!(
 7651            editor.selections.display_ranges(cx),
 7652            vec![
 7653                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 7654                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 7655            ]
 7656        );
 7657    });
 7658
 7659    _ = editor.update(cx, |editor, window, cx| {
 7660        editor.select_line(&SelectLine, window, cx);
 7661        assert_eq!(
 7662            editor.selections.display_ranges(cx),
 7663            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 7664        );
 7665    });
 7666}
 7667
 7668#[gpui::test]
 7669async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 7670    init_test(cx, |_| {});
 7671    let mut cx = EditorTestContext::new(cx).await;
 7672
 7673    #[track_caller]
 7674    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 7675        cx.set_state(initial_state);
 7676        cx.update_editor(|e, window, cx| {
 7677            e.split_selection_into_lines(&Default::default(), window, cx)
 7678        });
 7679        cx.assert_editor_state(expected_state);
 7680    }
 7681
 7682    // Selection starts and ends at the middle of lines, left-to-right
 7683    test(
 7684        &mut cx,
 7685        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 7686        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7687    );
 7688    // Same thing, right-to-left
 7689    test(
 7690        &mut cx,
 7691        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 7692        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7693    );
 7694
 7695    // Whole buffer, left-to-right, last line *doesn't* end with newline
 7696    test(
 7697        &mut cx,
 7698        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 7699        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7700    );
 7701    // Same thing, right-to-left
 7702    test(
 7703        &mut cx,
 7704        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 7705        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7706    );
 7707
 7708    // Whole buffer, left-to-right, last line ends with newline
 7709    test(
 7710        &mut cx,
 7711        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 7712        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7713    );
 7714    // Same thing, right-to-left
 7715    test(
 7716        &mut cx,
 7717        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 7718        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7719    );
 7720
 7721    // Starts at the end of a line, ends at the start of another
 7722    test(
 7723        &mut cx,
 7724        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 7725        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 7726    );
 7727}
 7728
 7729#[gpui::test]
 7730async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 7731    init_test(cx, |_| {});
 7732
 7733    let editor = cx.add_window(|window, cx| {
 7734        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 7735        build_editor(buffer, window, cx)
 7736    });
 7737
 7738    // setup
 7739    _ = editor.update(cx, |editor, window, cx| {
 7740        editor.fold_creases(
 7741            vec![
 7742                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 7743                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 7744                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 7745            ],
 7746            true,
 7747            window,
 7748            cx,
 7749        );
 7750        assert_eq!(
 7751            editor.display_text(cx),
 7752            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7753        );
 7754    });
 7755
 7756    _ = editor.update(cx, |editor, window, cx| {
 7757        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7758            s.select_display_ranges([
 7759                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7760                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7761                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7762                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 7763            ])
 7764        });
 7765        editor.split_selection_into_lines(&Default::default(), window, cx);
 7766        assert_eq!(
 7767            editor.display_text(cx),
 7768            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7769        );
 7770    });
 7771    EditorTestContext::for_editor(editor, cx)
 7772        .await
 7773        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 7774
 7775    _ = editor.update(cx, |editor, window, cx| {
 7776        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7777            s.select_display_ranges([
 7778                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 7779            ])
 7780        });
 7781        editor.split_selection_into_lines(&Default::default(), window, cx);
 7782        assert_eq!(
 7783            editor.display_text(cx),
 7784            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 7785        );
 7786        assert_eq!(
 7787            editor.selections.display_ranges(cx),
 7788            [
 7789                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 7790                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 7791                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 7792                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 7793                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 7794                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 7795                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 7796            ]
 7797        );
 7798    });
 7799    EditorTestContext::for_editor(editor, cx)
 7800        .await
 7801        .assert_editor_state(
 7802            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 7803        );
 7804}
 7805
 7806#[gpui::test]
 7807async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 7808    init_test(cx, |_| {});
 7809
 7810    let mut cx = EditorTestContext::new(cx).await;
 7811
 7812    cx.set_state(indoc!(
 7813        r#"abc
 7814           defˇghi
 7815
 7816           jk
 7817           nlmo
 7818           "#
 7819    ));
 7820
 7821    cx.update_editor(|editor, window, cx| {
 7822        editor.add_selection_above(&Default::default(), window, cx);
 7823    });
 7824
 7825    cx.assert_editor_state(indoc!(
 7826        r#"abcˇ
 7827           defˇghi
 7828
 7829           jk
 7830           nlmo
 7831           "#
 7832    ));
 7833
 7834    cx.update_editor(|editor, window, cx| {
 7835        editor.add_selection_above(&Default::default(), window, cx);
 7836    });
 7837
 7838    cx.assert_editor_state(indoc!(
 7839        r#"abcˇ
 7840            defˇghi
 7841
 7842            jk
 7843            nlmo
 7844            "#
 7845    ));
 7846
 7847    cx.update_editor(|editor, window, cx| {
 7848        editor.add_selection_below(&Default::default(), window, cx);
 7849    });
 7850
 7851    cx.assert_editor_state(indoc!(
 7852        r#"abc
 7853           defˇghi
 7854
 7855           jk
 7856           nlmo
 7857           "#
 7858    ));
 7859
 7860    cx.update_editor(|editor, window, cx| {
 7861        editor.undo_selection(&Default::default(), window, cx);
 7862    });
 7863
 7864    cx.assert_editor_state(indoc!(
 7865        r#"abcˇ
 7866           defˇghi
 7867
 7868           jk
 7869           nlmo
 7870           "#
 7871    ));
 7872
 7873    cx.update_editor(|editor, window, cx| {
 7874        editor.redo_selection(&Default::default(), window, cx);
 7875    });
 7876
 7877    cx.assert_editor_state(indoc!(
 7878        r#"abc
 7879           defˇghi
 7880
 7881           jk
 7882           nlmo
 7883           "#
 7884    ));
 7885
 7886    cx.update_editor(|editor, window, cx| {
 7887        editor.add_selection_below(&Default::default(), window, cx);
 7888    });
 7889
 7890    cx.assert_editor_state(indoc!(
 7891        r#"abc
 7892           defˇghi
 7893           ˇ
 7894           jk
 7895           nlmo
 7896           "#
 7897    ));
 7898
 7899    cx.update_editor(|editor, window, cx| {
 7900        editor.add_selection_below(&Default::default(), window, cx);
 7901    });
 7902
 7903    cx.assert_editor_state(indoc!(
 7904        r#"abc
 7905           defˇghi
 7906           ˇ
 7907           jkˇ
 7908           nlmo
 7909           "#
 7910    ));
 7911
 7912    cx.update_editor(|editor, window, cx| {
 7913        editor.add_selection_below(&Default::default(), window, cx);
 7914    });
 7915
 7916    cx.assert_editor_state(indoc!(
 7917        r#"abc
 7918           defˇghi
 7919           ˇ
 7920           jkˇ
 7921           nlmˇo
 7922           "#
 7923    ));
 7924
 7925    cx.update_editor(|editor, window, cx| {
 7926        editor.add_selection_below(&Default::default(), window, cx);
 7927    });
 7928
 7929    cx.assert_editor_state(indoc!(
 7930        r#"abc
 7931           defˇghi
 7932           ˇ
 7933           jkˇ
 7934           nlmˇo
 7935           ˇ"#
 7936    ));
 7937
 7938    // change selections
 7939    cx.set_state(indoc!(
 7940        r#"abc
 7941           def«ˇg»hi
 7942
 7943           jk
 7944           nlmo
 7945           "#
 7946    ));
 7947
 7948    cx.update_editor(|editor, window, cx| {
 7949        editor.add_selection_below(&Default::default(), window, cx);
 7950    });
 7951
 7952    cx.assert_editor_state(indoc!(
 7953        r#"abc
 7954           def«ˇg»hi
 7955
 7956           jk
 7957           nlm«ˇo»
 7958           "#
 7959    ));
 7960
 7961    cx.update_editor(|editor, window, cx| {
 7962        editor.add_selection_below(&Default::default(), window, cx);
 7963    });
 7964
 7965    cx.assert_editor_state(indoc!(
 7966        r#"abc
 7967           def«ˇg»hi
 7968
 7969           jk
 7970           nlm«ˇo»
 7971           "#
 7972    ));
 7973
 7974    cx.update_editor(|editor, window, cx| {
 7975        editor.add_selection_above(&Default::default(), window, cx);
 7976    });
 7977
 7978    cx.assert_editor_state(indoc!(
 7979        r#"abc
 7980           def«ˇg»hi
 7981
 7982           jk
 7983           nlmo
 7984           "#
 7985    ));
 7986
 7987    cx.update_editor(|editor, window, cx| {
 7988        editor.add_selection_above(&Default::default(), window, cx);
 7989    });
 7990
 7991    cx.assert_editor_state(indoc!(
 7992        r#"abc
 7993           def«ˇg»hi
 7994
 7995           jk
 7996           nlmo
 7997           "#
 7998    ));
 7999
 8000    // Change selections again
 8001    cx.set_state(indoc!(
 8002        r#"a«bc
 8003           defgˇ»hi
 8004
 8005           jk
 8006           nlmo
 8007           "#
 8008    ));
 8009
 8010    cx.update_editor(|editor, window, cx| {
 8011        editor.add_selection_below(&Default::default(), window, cx);
 8012    });
 8013
 8014    cx.assert_editor_state(indoc!(
 8015        r#"a«bcˇ»
 8016           d«efgˇ»hi
 8017
 8018           j«kˇ»
 8019           nlmo
 8020           "#
 8021    ));
 8022
 8023    cx.update_editor(|editor, window, cx| {
 8024        editor.add_selection_below(&Default::default(), window, cx);
 8025    });
 8026    cx.assert_editor_state(indoc!(
 8027        r#"a«bcˇ»
 8028           d«efgˇ»hi
 8029
 8030           j«kˇ»
 8031           n«lmoˇ»
 8032           "#
 8033    ));
 8034    cx.update_editor(|editor, window, cx| {
 8035        editor.add_selection_above(&Default::default(), window, cx);
 8036    });
 8037
 8038    cx.assert_editor_state(indoc!(
 8039        r#"a«bcˇ»
 8040           d«efgˇ»hi
 8041
 8042           j«kˇ»
 8043           nlmo
 8044           "#
 8045    ));
 8046
 8047    // Change selections again
 8048    cx.set_state(indoc!(
 8049        r#"abc
 8050           d«ˇefghi
 8051
 8052           jk
 8053           nlm»o
 8054           "#
 8055    ));
 8056
 8057    cx.update_editor(|editor, window, cx| {
 8058        editor.add_selection_above(&Default::default(), window, cx);
 8059    });
 8060
 8061    cx.assert_editor_state(indoc!(
 8062        r#"a«ˇbc»
 8063           d«ˇef»ghi
 8064
 8065           j«ˇk»
 8066           n«ˇlm»o
 8067           "#
 8068    ));
 8069
 8070    cx.update_editor(|editor, window, cx| {
 8071        editor.add_selection_below(&Default::default(), window, cx);
 8072    });
 8073
 8074    cx.assert_editor_state(indoc!(
 8075        r#"abc
 8076           d«ˇef»ghi
 8077
 8078           j«ˇk»
 8079           n«ˇlm»o
 8080           "#
 8081    ));
 8082}
 8083
 8084#[gpui::test]
 8085async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 8086    init_test(cx, |_| {});
 8087    let mut cx = EditorTestContext::new(cx).await;
 8088
 8089    cx.set_state(indoc!(
 8090        r#"line onˇe
 8091           liˇne two
 8092           line three
 8093           line four"#
 8094    ));
 8095
 8096    cx.update_editor(|editor, window, cx| {
 8097        editor.add_selection_below(&Default::default(), window, cx);
 8098    });
 8099
 8100    // test multiple cursors expand in the same direction
 8101    cx.assert_editor_state(indoc!(
 8102        r#"line onˇe
 8103           liˇne twˇo
 8104           liˇne three
 8105           line four"#
 8106    ));
 8107
 8108    cx.update_editor(|editor, window, cx| {
 8109        editor.add_selection_below(&Default::default(), window, cx);
 8110    });
 8111
 8112    cx.update_editor(|editor, window, cx| {
 8113        editor.add_selection_below(&Default::default(), window, cx);
 8114    });
 8115
 8116    // test multiple cursors expand below overflow
 8117    cx.assert_editor_state(indoc!(
 8118        r#"line onˇe
 8119           liˇne twˇo
 8120           liˇne thˇree
 8121           liˇne foˇur"#
 8122    ));
 8123
 8124    cx.update_editor(|editor, window, cx| {
 8125        editor.add_selection_above(&Default::default(), window, cx);
 8126    });
 8127
 8128    // test multiple cursors retrieves back correctly
 8129    cx.assert_editor_state(indoc!(
 8130        r#"line onˇe
 8131           liˇne twˇo
 8132           liˇne thˇree
 8133           line four"#
 8134    ));
 8135
 8136    cx.update_editor(|editor, window, cx| {
 8137        editor.add_selection_above(&Default::default(), window, cx);
 8138    });
 8139
 8140    cx.update_editor(|editor, window, cx| {
 8141        editor.add_selection_above(&Default::default(), window, cx);
 8142    });
 8143
 8144    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 8145    cx.assert_editor_state(indoc!(
 8146        r#"liˇne onˇe
 8147           liˇne two
 8148           line three
 8149           line four"#
 8150    ));
 8151
 8152    cx.update_editor(|editor, window, cx| {
 8153        editor.undo_selection(&Default::default(), window, cx);
 8154    });
 8155
 8156    // test undo
 8157    cx.assert_editor_state(indoc!(
 8158        r#"line onˇe
 8159           liˇne twˇo
 8160           line three
 8161           line four"#
 8162    ));
 8163
 8164    cx.update_editor(|editor, window, cx| {
 8165        editor.redo_selection(&Default::default(), window, cx);
 8166    });
 8167
 8168    // test redo
 8169    cx.assert_editor_state(indoc!(
 8170        r#"liˇne onˇe
 8171           liˇne two
 8172           line three
 8173           line four"#
 8174    ));
 8175
 8176    cx.set_state(indoc!(
 8177        r#"abcd
 8178           ef«ghˇ»
 8179           ijkl
 8180           «mˇ»nop"#
 8181    ));
 8182
 8183    cx.update_editor(|editor, window, cx| {
 8184        editor.add_selection_above(&Default::default(), window, cx);
 8185    });
 8186
 8187    // test multiple selections expand in the same direction
 8188    cx.assert_editor_state(indoc!(
 8189        r#"ab«cdˇ»
 8190           ef«ghˇ»
 8191           «iˇ»jkl
 8192           «mˇ»nop"#
 8193    ));
 8194
 8195    cx.update_editor(|editor, window, cx| {
 8196        editor.add_selection_above(&Default::default(), window, cx);
 8197    });
 8198
 8199    // test multiple selection upward overflow
 8200    cx.assert_editor_state(indoc!(
 8201        r#"ab«cdˇ»
 8202           «eˇ»f«ghˇ»
 8203           «iˇ»jkl
 8204           «mˇ»nop"#
 8205    ));
 8206
 8207    cx.update_editor(|editor, window, cx| {
 8208        editor.add_selection_below(&Default::default(), window, cx);
 8209    });
 8210
 8211    // test multiple selection retrieves back correctly
 8212    cx.assert_editor_state(indoc!(
 8213        r#"abcd
 8214           ef«ghˇ»
 8215           «iˇ»jkl
 8216           «mˇ»nop"#
 8217    ));
 8218
 8219    cx.update_editor(|editor, window, cx| {
 8220        editor.add_selection_below(&Default::default(), window, cx);
 8221    });
 8222
 8223    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 8224    cx.assert_editor_state(indoc!(
 8225        r#"abcd
 8226           ef«ghˇ»
 8227           ij«klˇ»
 8228           «mˇ»nop"#
 8229    ));
 8230
 8231    cx.update_editor(|editor, window, cx| {
 8232        editor.undo_selection(&Default::default(), window, cx);
 8233    });
 8234
 8235    // test undo
 8236    cx.assert_editor_state(indoc!(
 8237        r#"abcd
 8238           ef«ghˇ»
 8239           «iˇ»jkl
 8240           «mˇ»nop"#
 8241    ));
 8242
 8243    cx.update_editor(|editor, window, cx| {
 8244        editor.redo_selection(&Default::default(), window, cx);
 8245    });
 8246
 8247    // test redo
 8248    cx.assert_editor_state(indoc!(
 8249        r#"abcd
 8250           ef«ghˇ»
 8251           ij«klˇ»
 8252           «mˇ»nop"#
 8253    ));
 8254}
 8255
 8256#[gpui::test]
 8257async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 8258    init_test(cx, |_| {});
 8259    let mut cx = EditorTestContext::new(cx).await;
 8260
 8261    cx.set_state(indoc!(
 8262        r#"line onˇe
 8263           liˇne two
 8264           line three
 8265           line four"#
 8266    ));
 8267
 8268    cx.update_editor(|editor, window, cx| {
 8269        editor.add_selection_below(&Default::default(), window, cx);
 8270        editor.add_selection_below(&Default::default(), window, cx);
 8271        editor.add_selection_below(&Default::default(), window, cx);
 8272    });
 8273
 8274    // initial state with two multi cursor groups
 8275    cx.assert_editor_state(indoc!(
 8276        r#"line onˇe
 8277           liˇne twˇo
 8278           liˇne thˇree
 8279           liˇne foˇur"#
 8280    ));
 8281
 8282    // add single cursor in middle - simulate opt click
 8283    cx.update_editor(|editor, window, cx| {
 8284        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 8285        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 8286        editor.end_selection(window, cx);
 8287    });
 8288
 8289    cx.assert_editor_state(indoc!(
 8290        r#"line onˇe
 8291           liˇne twˇo
 8292           liˇneˇ thˇree
 8293           liˇne foˇur"#
 8294    ));
 8295
 8296    cx.update_editor(|editor, window, cx| {
 8297        editor.add_selection_above(&Default::default(), window, cx);
 8298    });
 8299
 8300    // test new added selection expands above and existing selection shrinks
 8301    cx.assert_editor_state(indoc!(
 8302        r#"line onˇe
 8303           liˇneˇ twˇo
 8304           liˇneˇ thˇree
 8305           line four"#
 8306    ));
 8307
 8308    cx.update_editor(|editor, window, cx| {
 8309        editor.add_selection_above(&Default::default(), window, cx);
 8310    });
 8311
 8312    // test new added selection expands above and existing selection shrinks
 8313    cx.assert_editor_state(indoc!(
 8314        r#"lineˇ onˇe
 8315           liˇneˇ twˇo
 8316           lineˇ three
 8317           line four"#
 8318    ));
 8319
 8320    // intial state with two selection groups
 8321    cx.set_state(indoc!(
 8322        r#"abcd
 8323           ef«ghˇ»
 8324           ijkl
 8325           «mˇ»nop"#
 8326    ));
 8327
 8328    cx.update_editor(|editor, window, cx| {
 8329        editor.add_selection_above(&Default::default(), window, cx);
 8330        editor.add_selection_above(&Default::default(), window, cx);
 8331    });
 8332
 8333    cx.assert_editor_state(indoc!(
 8334        r#"ab«cdˇ»
 8335           «eˇ»f«ghˇ»
 8336           «iˇ»jkl
 8337           «mˇ»nop"#
 8338    ));
 8339
 8340    // add single selection in middle - simulate opt drag
 8341    cx.update_editor(|editor, window, cx| {
 8342        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 8343        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 8344        editor.update_selection(
 8345            DisplayPoint::new(DisplayRow(2), 4),
 8346            0,
 8347            gpui::Point::<f32>::default(),
 8348            window,
 8349            cx,
 8350        );
 8351        editor.end_selection(window, cx);
 8352    });
 8353
 8354    cx.assert_editor_state(indoc!(
 8355        r#"ab«cdˇ»
 8356           «eˇ»f«ghˇ»
 8357           «iˇ»jk«lˇ»
 8358           «mˇ»nop"#
 8359    ));
 8360
 8361    cx.update_editor(|editor, window, cx| {
 8362        editor.add_selection_below(&Default::default(), window, cx);
 8363    });
 8364
 8365    // test new added selection expands below, others shrinks from above
 8366    cx.assert_editor_state(indoc!(
 8367        r#"abcd
 8368           ef«ghˇ»
 8369           «iˇ»jk«lˇ»
 8370           «mˇ»no«pˇ»"#
 8371    ));
 8372}
 8373
 8374#[gpui::test]
 8375async fn test_select_next(cx: &mut TestAppContext) {
 8376    init_test(cx, |_| {});
 8377
 8378    let mut cx = EditorTestContext::new(cx).await;
 8379    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8380
 8381    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8382        .unwrap();
 8383    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8384
 8385    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8386        .unwrap();
 8387    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8388
 8389    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8390    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8391
 8392    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8393    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8394
 8395    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8396        .unwrap();
 8397    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8398
 8399    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8400        .unwrap();
 8401    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8402
 8403    // Test selection direction should be preserved
 8404    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8405
 8406    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8407        .unwrap();
 8408    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 8409}
 8410
 8411#[gpui::test]
 8412async fn test_select_all_matches(cx: &mut TestAppContext) {
 8413    init_test(cx, |_| {});
 8414
 8415    let mut cx = EditorTestContext::new(cx).await;
 8416
 8417    // Test caret-only selections
 8418    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8419    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8420        .unwrap();
 8421    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8422
 8423    // Test left-to-right selections
 8424    cx.set_state("abc\n«abcˇ»\nabc");
 8425    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8426        .unwrap();
 8427    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 8428
 8429    // Test right-to-left selections
 8430    cx.set_state("abc\n«ˇabc»\nabc");
 8431    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8432        .unwrap();
 8433    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 8434
 8435    // Test selecting whitespace with caret selection
 8436    cx.set_state("abc\nˇ   abc\nabc");
 8437    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8438        .unwrap();
 8439    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 8440
 8441    // Test selecting whitespace with left-to-right selection
 8442    cx.set_state("abc\n«ˇ  »abc\nabc");
 8443    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8444        .unwrap();
 8445    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 8446
 8447    // Test no matches with right-to-left selection
 8448    cx.set_state("abc\n«  ˇ»abc\nabc");
 8449    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8450        .unwrap();
 8451    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 8452
 8453    // Test with a single word and clip_at_line_ends=true (#29823)
 8454    cx.set_state("aˇbc");
 8455    cx.update_editor(|e, window, cx| {
 8456        e.set_clip_at_line_ends(true, cx);
 8457        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8458        e.set_clip_at_line_ends(false, cx);
 8459    });
 8460    cx.assert_editor_state("«abcˇ»");
 8461}
 8462
 8463#[gpui::test]
 8464async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 8465    init_test(cx, |_| {});
 8466
 8467    let mut cx = EditorTestContext::new(cx).await;
 8468
 8469    let large_body_1 = "\nd".repeat(200);
 8470    let large_body_2 = "\ne".repeat(200);
 8471
 8472    cx.set_state(&format!(
 8473        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 8474    ));
 8475    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 8476        let scroll_position = editor.scroll_position(cx);
 8477        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 8478        scroll_position
 8479    });
 8480
 8481    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8482        .unwrap();
 8483    cx.assert_editor_state(&format!(
 8484        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 8485    ));
 8486    let scroll_position_after_selection =
 8487        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 8488    assert_eq!(
 8489        initial_scroll_position, scroll_position_after_selection,
 8490        "Scroll position should not change after selecting all matches"
 8491    );
 8492}
 8493
 8494#[gpui::test]
 8495async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 8496    init_test(cx, |_| {});
 8497
 8498    let mut cx = EditorLspTestContext::new_rust(
 8499        lsp::ServerCapabilities {
 8500            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8501            ..Default::default()
 8502        },
 8503        cx,
 8504    )
 8505    .await;
 8506
 8507    cx.set_state(indoc! {"
 8508        line 1
 8509        line 2
 8510        linˇe 3
 8511        line 4
 8512        line 5
 8513    "});
 8514
 8515    // Make an edit
 8516    cx.update_editor(|editor, window, cx| {
 8517        editor.handle_input("X", window, cx);
 8518    });
 8519
 8520    // Move cursor to a different position
 8521    cx.update_editor(|editor, window, cx| {
 8522        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8523            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 8524        });
 8525    });
 8526
 8527    cx.assert_editor_state(indoc! {"
 8528        line 1
 8529        line 2
 8530        linXe 3
 8531        line 4
 8532        liˇne 5
 8533    "});
 8534
 8535    cx.lsp
 8536        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 8537            Ok(Some(vec![lsp::TextEdit::new(
 8538                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 8539                "PREFIX ".to_string(),
 8540            )]))
 8541        });
 8542
 8543    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 8544        .unwrap()
 8545        .await
 8546        .unwrap();
 8547
 8548    cx.assert_editor_state(indoc! {"
 8549        PREFIX line 1
 8550        line 2
 8551        linXe 3
 8552        line 4
 8553        liˇne 5
 8554    "});
 8555
 8556    // Undo formatting
 8557    cx.update_editor(|editor, window, cx| {
 8558        editor.undo(&Default::default(), window, cx);
 8559    });
 8560
 8561    // Verify cursor moved back to position after edit
 8562    cx.assert_editor_state(indoc! {"
 8563        line 1
 8564        line 2
 8565        linXˇe 3
 8566        line 4
 8567        line 5
 8568    "});
 8569}
 8570
 8571#[gpui::test]
 8572async fn test_undo_edit_prediction_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 8573    init_test(cx, |_| {});
 8574
 8575    let mut cx = EditorTestContext::new(cx).await;
 8576
 8577    let provider = cx.new(|_| FakeEditPredictionProvider::default());
 8578    cx.update_editor(|editor, window, cx| {
 8579        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 8580    });
 8581
 8582    cx.set_state(indoc! {"
 8583        line 1
 8584        line 2
 8585        linˇe 3
 8586        line 4
 8587        line 5
 8588        line 6
 8589        line 7
 8590        line 8
 8591        line 9
 8592        line 10
 8593    "});
 8594
 8595    let snapshot = cx.buffer_snapshot();
 8596    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 8597
 8598    cx.update(|_, cx| {
 8599        provider.update(cx, |provider, _| {
 8600            provider.set_edit_prediction(Some(edit_prediction::EditPrediction::Local {
 8601                id: None,
 8602                edits: vec![(edit_position..edit_position, "X".into())],
 8603                edit_preview: None,
 8604            }))
 8605        })
 8606    });
 8607
 8608    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
 8609    cx.update_editor(|editor, window, cx| {
 8610        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 8611    });
 8612
 8613    cx.assert_editor_state(indoc! {"
 8614        line 1
 8615        line 2
 8616        lineXˇ 3
 8617        line 4
 8618        line 5
 8619        line 6
 8620        line 7
 8621        line 8
 8622        line 9
 8623        line 10
 8624    "});
 8625
 8626    cx.update_editor(|editor, window, cx| {
 8627        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8628            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 8629        });
 8630    });
 8631
 8632    cx.assert_editor_state(indoc! {"
 8633        line 1
 8634        line 2
 8635        lineX 3
 8636        line 4
 8637        line 5
 8638        line 6
 8639        line 7
 8640        line 8
 8641        line 9
 8642        liˇne 10
 8643    "});
 8644
 8645    cx.update_editor(|editor, window, cx| {
 8646        editor.undo(&Default::default(), window, cx);
 8647    });
 8648
 8649    cx.assert_editor_state(indoc! {"
 8650        line 1
 8651        line 2
 8652        lineˇ 3
 8653        line 4
 8654        line 5
 8655        line 6
 8656        line 7
 8657        line 8
 8658        line 9
 8659        line 10
 8660    "});
 8661}
 8662
 8663#[gpui::test]
 8664async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 8665    init_test(cx, |_| {});
 8666
 8667    let mut cx = EditorTestContext::new(cx).await;
 8668    cx.set_state(
 8669        r#"let foo = 2;
 8670lˇet foo = 2;
 8671let fooˇ = 2;
 8672let foo = 2;
 8673let foo = ˇ2;"#,
 8674    );
 8675
 8676    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8677        .unwrap();
 8678    cx.assert_editor_state(
 8679        r#"let foo = 2;
 8680«letˇ» foo = 2;
 8681let «fooˇ» = 2;
 8682let foo = 2;
 8683let foo = «2ˇ»;"#,
 8684    );
 8685
 8686    // noop for multiple selections with different contents
 8687    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8688        .unwrap();
 8689    cx.assert_editor_state(
 8690        r#"let foo = 2;
 8691«letˇ» foo = 2;
 8692let «fooˇ» = 2;
 8693let foo = 2;
 8694let foo = «2ˇ»;"#,
 8695    );
 8696
 8697    // Test last selection direction should be preserved
 8698    cx.set_state(
 8699        r#"let foo = 2;
 8700let foo = 2;
 8701let «fooˇ» = 2;
 8702let «ˇfoo» = 2;
 8703let foo = 2;"#,
 8704    );
 8705
 8706    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8707        .unwrap();
 8708    cx.assert_editor_state(
 8709        r#"let foo = 2;
 8710let foo = 2;
 8711let «fooˇ» = 2;
 8712let «ˇfoo» = 2;
 8713let «ˇfoo» = 2;"#,
 8714    );
 8715}
 8716
 8717#[gpui::test]
 8718async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 8719    init_test(cx, |_| {});
 8720
 8721    let mut cx =
 8722        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 8723
 8724    cx.assert_editor_state(indoc! {"
 8725        ˇbbb
 8726        ccc
 8727
 8728        bbb
 8729        ccc
 8730        "});
 8731    cx.dispatch_action(SelectPrevious::default());
 8732    cx.assert_editor_state(indoc! {"
 8733                «bbbˇ»
 8734                ccc
 8735
 8736                bbb
 8737                ccc
 8738                "});
 8739    cx.dispatch_action(SelectPrevious::default());
 8740    cx.assert_editor_state(indoc! {"
 8741                «bbbˇ»
 8742                ccc
 8743
 8744                «bbbˇ»
 8745                ccc
 8746                "});
 8747}
 8748
 8749#[gpui::test]
 8750async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 8751    init_test(cx, |_| {});
 8752
 8753    let mut cx = EditorTestContext::new(cx).await;
 8754    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8755
 8756    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8757        .unwrap();
 8758    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8759
 8760    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8761        .unwrap();
 8762    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8763
 8764    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8765    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8766
 8767    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8768    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8769
 8770    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8771        .unwrap();
 8772    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 8773
 8774    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8775        .unwrap();
 8776    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8777}
 8778
 8779#[gpui::test]
 8780async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 8781    init_test(cx, |_| {});
 8782
 8783    let mut cx = EditorTestContext::new(cx).await;
 8784    cx.set_state("");
 8785
 8786    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8787        .unwrap();
 8788    cx.assert_editor_state("«aˇ»");
 8789    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8790        .unwrap();
 8791    cx.assert_editor_state("«aˇ»");
 8792}
 8793
 8794#[gpui::test]
 8795async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 8796    init_test(cx, |_| {});
 8797
 8798    let mut cx = EditorTestContext::new(cx).await;
 8799    cx.set_state(
 8800        r#"let foo = 2;
 8801lˇet foo = 2;
 8802let fooˇ = 2;
 8803let foo = 2;
 8804let foo = ˇ2;"#,
 8805    );
 8806
 8807    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8808        .unwrap();
 8809    cx.assert_editor_state(
 8810        r#"let foo = 2;
 8811«letˇ» foo = 2;
 8812let «fooˇ» = 2;
 8813let foo = 2;
 8814let foo = «2ˇ»;"#,
 8815    );
 8816
 8817    // noop for multiple selections with different contents
 8818    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8819        .unwrap();
 8820    cx.assert_editor_state(
 8821        r#"let foo = 2;
 8822«letˇ» foo = 2;
 8823let «fooˇ» = 2;
 8824let foo = 2;
 8825let foo = «2ˇ»;"#,
 8826    );
 8827}
 8828
 8829#[gpui::test]
 8830async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 8831    init_test(cx, |_| {});
 8832
 8833    let mut cx = EditorTestContext::new(cx).await;
 8834    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8835
 8836    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8837        .unwrap();
 8838    // selection direction is preserved
 8839    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 8840
 8841    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8842        .unwrap();
 8843    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 8844
 8845    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8846    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 8847
 8848    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8849    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 8850
 8851    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8852        .unwrap();
 8853    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 8854
 8855    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8856        .unwrap();
 8857    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 8858}
 8859
 8860#[gpui::test]
 8861async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 8862    init_test(cx, |_| {});
 8863
 8864    let language = Arc::new(Language::new(
 8865        LanguageConfig::default(),
 8866        Some(tree_sitter_rust::LANGUAGE.into()),
 8867    ));
 8868
 8869    let text = r#"
 8870        use mod1::mod2::{mod3, mod4};
 8871
 8872        fn fn_1(param1: bool, param2: &str) {
 8873            let var1 = "text";
 8874        }
 8875    "#
 8876    .unindent();
 8877
 8878    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8879    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8880    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8881
 8882    editor
 8883        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8884        .await;
 8885
 8886    editor.update_in(cx, |editor, window, cx| {
 8887        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8888            s.select_display_ranges([
 8889                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 8890                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 8891                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 8892            ]);
 8893        });
 8894        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8895    });
 8896    editor.update(cx, |editor, cx| {
 8897        assert_text_with_selections(
 8898            editor,
 8899            indoc! {r#"
 8900                use mod1::mod2::{mod3, «mod4ˇ»};
 8901
 8902                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8903                    let var1 = "«ˇtext»";
 8904                }
 8905            "#},
 8906            cx,
 8907        );
 8908    });
 8909
 8910    editor.update_in(cx, |editor, window, cx| {
 8911        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8912    });
 8913    editor.update(cx, |editor, cx| {
 8914        assert_text_with_selections(
 8915            editor,
 8916            indoc! {r#"
 8917                use mod1::mod2::«{mod3, mod4}ˇ»;
 8918
 8919                «ˇfn fn_1(param1: bool, param2: &str) {
 8920                    let var1 = "text";
 8921 8922            "#},
 8923            cx,
 8924        );
 8925    });
 8926
 8927    editor.update_in(cx, |editor, window, cx| {
 8928        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8929    });
 8930    assert_eq!(
 8931        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 8932        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 8933    );
 8934
 8935    // Trying to expand the selected syntax node one more time has no effect.
 8936    editor.update_in(cx, |editor, window, cx| {
 8937        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8938    });
 8939    assert_eq!(
 8940        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 8941        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 8942    );
 8943
 8944    editor.update_in(cx, |editor, window, cx| {
 8945        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8946    });
 8947    editor.update(cx, |editor, cx| {
 8948        assert_text_with_selections(
 8949            editor,
 8950            indoc! {r#"
 8951                use mod1::mod2::«{mod3, mod4}ˇ»;
 8952
 8953                «ˇfn fn_1(param1: bool, param2: &str) {
 8954                    let var1 = "text";
 8955 8956            "#},
 8957            cx,
 8958        );
 8959    });
 8960
 8961    editor.update_in(cx, |editor, window, cx| {
 8962        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8963    });
 8964    editor.update(cx, |editor, cx| {
 8965        assert_text_with_selections(
 8966            editor,
 8967            indoc! {r#"
 8968                use mod1::mod2::{mod3, «mod4ˇ»};
 8969
 8970                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8971                    let var1 = "«ˇtext»";
 8972                }
 8973            "#},
 8974            cx,
 8975        );
 8976    });
 8977
 8978    editor.update_in(cx, |editor, window, cx| {
 8979        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8980    });
 8981    editor.update(cx, |editor, cx| {
 8982        assert_text_with_selections(
 8983            editor,
 8984            indoc! {r#"
 8985                use mod1::mod2::{mod3, moˇd4};
 8986
 8987                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 8988                    let var1 = "teˇxt";
 8989                }
 8990            "#},
 8991            cx,
 8992        );
 8993    });
 8994
 8995    // Trying to shrink the selected syntax node one more time has no effect.
 8996    editor.update_in(cx, |editor, window, cx| {
 8997        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8998    });
 8999    editor.update_in(cx, |editor, _, cx| {
 9000        assert_text_with_selections(
 9001            editor,
 9002            indoc! {r#"
 9003                use mod1::mod2::{mod3, moˇd4};
 9004
 9005                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 9006                    let var1 = "teˇxt";
 9007                }
 9008            "#},
 9009            cx,
 9010        );
 9011    });
 9012
 9013    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 9014    // a fold.
 9015    editor.update_in(cx, |editor, window, cx| {
 9016        editor.fold_creases(
 9017            vec![
 9018                Crease::simple(
 9019                    Point::new(0, 21)..Point::new(0, 24),
 9020                    FoldPlaceholder::test(),
 9021                ),
 9022                Crease::simple(
 9023                    Point::new(3, 20)..Point::new(3, 22),
 9024                    FoldPlaceholder::test(),
 9025                ),
 9026            ],
 9027            true,
 9028            window,
 9029            cx,
 9030        );
 9031        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9032    });
 9033    editor.update(cx, |editor, cx| {
 9034        assert_text_with_selections(
 9035            editor,
 9036            indoc! {r#"
 9037                use mod1::mod2::«{mod3, mod4}ˇ»;
 9038
 9039                fn fn_1«ˇ(param1: bool, param2: &str)» {
 9040                    let var1 = "«ˇtext»";
 9041                }
 9042            "#},
 9043            cx,
 9044        );
 9045    });
 9046}
 9047
 9048#[gpui::test]
 9049async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 9050    init_test(cx, |_| {});
 9051
 9052    let language = Arc::new(Language::new(
 9053        LanguageConfig::default(),
 9054        Some(tree_sitter_rust::LANGUAGE.into()),
 9055    ));
 9056
 9057    let text = "let a = 2;";
 9058
 9059    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9060    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9061    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9062
 9063    editor
 9064        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9065        .await;
 9066
 9067    // Test case 1: Cursor at end of word
 9068    editor.update_in(cx, |editor, window, cx| {
 9069        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9070            s.select_display_ranges([
 9071                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 9072            ]);
 9073        });
 9074    });
 9075    editor.update(cx, |editor, cx| {
 9076        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 9077    });
 9078    editor.update_in(cx, |editor, window, cx| {
 9079        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9080    });
 9081    editor.update(cx, |editor, cx| {
 9082        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 9083    });
 9084    editor.update_in(cx, |editor, window, cx| {
 9085        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9086    });
 9087    editor.update(cx, |editor, cx| {
 9088        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 9089    });
 9090
 9091    // Test case 2: Cursor at end of statement
 9092    editor.update_in(cx, |editor, window, cx| {
 9093        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9094            s.select_display_ranges([
 9095                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 9096            ]);
 9097        });
 9098    });
 9099    editor.update(cx, |editor, cx| {
 9100        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 9101    });
 9102    editor.update_in(cx, |editor, window, cx| {
 9103        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9104    });
 9105    editor.update(cx, |editor, cx| {
 9106        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 9107    });
 9108}
 9109
 9110#[gpui::test]
 9111async fn test_select_larger_syntax_node_for_cursor_at_symbol(cx: &mut TestAppContext) {
 9112    init_test(cx, |_| {});
 9113
 9114    let language = Arc::new(Language::new(
 9115        LanguageConfig {
 9116            name: "JavaScript".into(),
 9117            ..Default::default()
 9118        },
 9119        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 9120    ));
 9121
 9122    let text = r#"
 9123        let a = {
 9124            key: "value",
 9125        };
 9126    "#
 9127    .unindent();
 9128
 9129    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9130    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9131    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9132
 9133    editor
 9134        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9135        .await;
 9136
 9137    // Test case 1: Cursor after '{'
 9138    editor.update_in(cx, |editor, window, cx| {
 9139        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9140            s.select_display_ranges([
 9141                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 9)
 9142            ]);
 9143        });
 9144    });
 9145    editor.update(cx, |editor, cx| {
 9146        assert_text_with_selections(
 9147            editor,
 9148            indoc! {r#"
 9149                let a = {ˇ
 9150                    key: "value",
 9151                };
 9152            "#},
 9153            cx,
 9154        );
 9155    });
 9156    editor.update_in(cx, |editor, window, cx| {
 9157        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9158    });
 9159    editor.update(cx, |editor, cx| {
 9160        assert_text_with_selections(
 9161            editor,
 9162            indoc! {r#"
 9163                let a = «ˇ{
 9164                    key: "value",
 9165                }»;
 9166            "#},
 9167            cx,
 9168        );
 9169    });
 9170
 9171    // Test case 2: Cursor after ':'
 9172    editor.update_in(cx, |editor, window, cx| {
 9173        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9174            s.select_display_ranges([
 9175                DisplayPoint::new(DisplayRow(1), 8)..DisplayPoint::new(DisplayRow(1), 8)
 9176            ]);
 9177        });
 9178    });
 9179    editor.update(cx, |editor, cx| {
 9180        assert_text_with_selections(
 9181            editor,
 9182            indoc! {r#"
 9183                let a = {
 9184                    key:ˇ "value",
 9185                };
 9186            "#},
 9187            cx,
 9188        );
 9189    });
 9190    editor.update_in(cx, |editor, window, cx| {
 9191        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9192    });
 9193    editor.update(cx, |editor, cx| {
 9194        assert_text_with_selections(
 9195            editor,
 9196            indoc! {r#"
 9197                let a = {
 9198                    «ˇkey: "value"»,
 9199                };
 9200            "#},
 9201            cx,
 9202        );
 9203    });
 9204    editor.update_in(cx, |editor, window, cx| {
 9205        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9206    });
 9207    editor.update(cx, |editor, cx| {
 9208        assert_text_with_selections(
 9209            editor,
 9210            indoc! {r#"
 9211                let a = «ˇ{
 9212                    key: "value",
 9213                }»;
 9214            "#},
 9215            cx,
 9216        );
 9217    });
 9218
 9219    // Test case 3: Cursor after ','
 9220    editor.update_in(cx, |editor, window, cx| {
 9221        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9222            s.select_display_ranges([
 9223                DisplayPoint::new(DisplayRow(1), 17)..DisplayPoint::new(DisplayRow(1), 17)
 9224            ]);
 9225        });
 9226    });
 9227    editor.update(cx, |editor, cx| {
 9228        assert_text_with_selections(
 9229            editor,
 9230            indoc! {r#"
 9231                let a = {
 9232                    key: "value",ˇ
 9233                };
 9234            "#},
 9235            cx,
 9236        );
 9237    });
 9238    editor.update_in(cx, |editor, window, cx| {
 9239        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9240    });
 9241    editor.update(cx, |editor, cx| {
 9242        assert_text_with_selections(
 9243            editor,
 9244            indoc! {r#"
 9245                let a = «ˇ{
 9246                    key: "value",
 9247                }»;
 9248            "#},
 9249            cx,
 9250        );
 9251    });
 9252
 9253    // Test case 4: Cursor after ';'
 9254    editor.update_in(cx, |editor, window, cx| {
 9255        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9256            s.select_display_ranges([
 9257                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)
 9258            ]);
 9259        });
 9260    });
 9261    editor.update(cx, |editor, cx| {
 9262        assert_text_with_selections(
 9263            editor,
 9264            indoc! {r#"
 9265                let a = {
 9266                    key: "value",
 9267                };ˇ
 9268            "#},
 9269            cx,
 9270        );
 9271    });
 9272    editor.update_in(cx, |editor, window, cx| {
 9273        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9274    });
 9275    editor.update(cx, |editor, cx| {
 9276        assert_text_with_selections(
 9277            editor,
 9278            indoc! {r#"
 9279                «ˇlet a = {
 9280                    key: "value",
 9281                };
 9282                »"#},
 9283            cx,
 9284        );
 9285    });
 9286}
 9287
 9288#[gpui::test]
 9289async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 9290    init_test(cx, |_| {});
 9291
 9292    let language = Arc::new(Language::new(
 9293        LanguageConfig::default(),
 9294        Some(tree_sitter_rust::LANGUAGE.into()),
 9295    ));
 9296
 9297    let text = r#"
 9298        use mod1::mod2::{mod3, mod4};
 9299
 9300        fn fn_1(param1: bool, param2: &str) {
 9301            let var1 = "hello world";
 9302        }
 9303    "#
 9304    .unindent();
 9305
 9306    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9307    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9308    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9309
 9310    editor
 9311        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9312        .await;
 9313
 9314    // Test 1: Cursor on a letter of a string word
 9315    editor.update_in(cx, |editor, window, cx| {
 9316        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9317            s.select_display_ranges([
 9318                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 9319            ]);
 9320        });
 9321    });
 9322    editor.update_in(cx, |editor, window, cx| {
 9323        assert_text_with_selections(
 9324            editor,
 9325            indoc! {r#"
 9326                use mod1::mod2::{mod3, mod4};
 9327
 9328                fn fn_1(param1: bool, param2: &str) {
 9329                    let var1 = "hˇello world";
 9330                }
 9331            "#},
 9332            cx,
 9333        );
 9334        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9335        assert_text_with_selections(
 9336            editor,
 9337            indoc! {r#"
 9338                use mod1::mod2::{mod3, mod4};
 9339
 9340                fn fn_1(param1: bool, param2: &str) {
 9341                    let var1 = "«ˇhello» world";
 9342                }
 9343            "#},
 9344            cx,
 9345        );
 9346    });
 9347
 9348    // Test 2: Partial selection within a word
 9349    editor.update_in(cx, |editor, window, cx| {
 9350        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9351            s.select_display_ranges([
 9352                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 9353            ]);
 9354        });
 9355    });
 9356    editor.update_in(cx, |editor, window, cx| {
 9357        assert_text_with_selections(
 9358            editor,
 9359            indoc! {r#"
 9360                use mod1::mod2::{mod3, mod4};
 9361
 9362                fn fn_1(param1: bool, param2: &str) {
 9363                    let var1 = "h«elˇ»lo world";
 9364                }
 9365            "#},
 9366            cx,
 9367        );
 9368        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9369        assert_text_with_selections(
 9370            editor,
 9371            indoc! {r#"
 9372                use mod1::mod2::{mod3, mod4};
 9373
 9374                fn fn_1(param1: bool, param2: &str) {
 9375                    let var1 = "«ˇhello» world";
 9376                }
 9377            "#},
 9378            cx,
 9379        );
 9380    });
 9381
 9382    // Test 3: Complete word already selected
 9383    editor.update_in(cx, |editor, window, cx| {
 9384        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9385            s.select_display_ranges([
 9386                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 9387            ]);
 9388        });
 9389    });
 9390    editor.update_in(cx, |editor, window, cx| {
 9391        assert_text_with_selections(
 9392            editor,
 9393            indoc! {r#"
 9394                use mod1::mod2::{mod3, mod4};
 9395
 9396                fn fn_1(param1: bool, param2: &str) {
 9397                    let var1 = "«helloˇ» world";
 9398                }
 9399            "#},
 9400            cx,
 9401        );
 9402        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9403        assert_text_with_selections(
 9404            editor,
 9405            indoc! {r#"
 9406                use mod1::mod2::{mod3, mod4};
 9407
 9408                fn fn_1(param1: bool, param2: &str) {
 9409                    let var1 = "«hello worldˇ»";
 9410                }
 9411            "#},
 9412            cx,
 9413        );
 9414    });
 9415
 9416    // Test 4: Selection spanning across words
 9417    editor.update_in(cx, |editor, window, cx| {
 9418        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9419            s.select_display_ranges([
 9420                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 9421            ]);
 9422        });
 9423    });
 9424    editor.update_in(cx, |editor, window, cx| {
 9425        assert_text_with_selections(
 9426            editor,
 9427            indoc! {r#"
 9428                use mod1::mod2::{mod3, mod4};
 9429
 9430                fn fn_1(param1: bool, param2: &str) {
 9431                    let var1 = "hel«lo woˇ»rld";
 9432                }
 9433            "#},
 9434            cx,
 9435        );
 9436        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9437        assert_text_with_selections(
 9438            editor,
 9439            indoc! {r#"
 9440                use mod1::mod2::{mod3, mod4};
 9441
 9442                fn fn_1(param1: bool, param2: &str) {
 9443                    let var1 = "«ˇhello world»";
 9444                }
 9445            "#},
 9446            cx,
 9447        );
 9448    });
 9449
 9450    // Test 5: Expansion beyond string
 9451    editor.update_in(cx, |editor, window, cx| {
 9452        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9453        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9454        assert_text_with_selections(
 9455            editor,
 9456            indoc! {r#"
 9457                use mod1::mod2::{mod3, mod4};
 9458
 9459                fn fn_1(param1: bool, param2: &str) {
 9460                    «ˇlet var1 = "hello world";»
 9461                }
 9462            "#},
 9463            cx,
 9464        );
 9465    });
 9466}
 9467
 9468#[gpui::test]
 9469async fn test_unwrap_syntax_nodes(cx: &mut gpui::TestAppContext) {
 9470    init_test(cx, |_| {});
 9471
 9472    let mut cx = EditorTestContext::new(cx).await;
 9473
 9474    let language = Arc::new(Language::new(
 9475        LanguageConfig::default(),
 9476        Some(tree_sitter_rust::LANGUAGE.into()),
 9477    ));
 9478
 9479    cx.update_buffer(|buffer, cx| {
 9480        buffer.set_language(Some(language), cx);
 9481    });
 9482
 9483    cx.set_state(indoc! { r#"use mod1::{mod2::{«mod3ˇ», mod4}, mod5::{mod6, «mod7ˇ»}};"# });
 9484    cx.update_editor(|editor, window, cx| {
 9485        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9486    });
 9487
 9488    cx.assert_editor_state(indoc! { r#"use mod1::{mod2::«mod3ˇ», mod5::«mod7ˇ»};"# });
 9489
 9490    cx.set_state(indoc! { r#"fn a() {
 9491          // what
 9492          // a
 9493          // ˇlong
 9494          // method
 9495          // I
 9496          // sure
 9497          // hope
 9498          // it
 9499          // works
 9500    }"# });
 9501
 9502    let buffer = cx.update_multibuffer(|multibuffer, _| multibuffer.as_singleton().unwrap());
 9503    let multi_buffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
 9504    cx.update(|_, cx| {
 9505        multi_buffer.update(cx, |multi_buffer, cx| {
 9506            multi_buffer.set_excerpts_for_path(
 9507                PathKey::for_buffer(&buffer, cx),
 9508                buffer,
 9509                [Point::new(1, 0)..Point::new(1, 0)],
 9510                3,
 9511                cx,
 9512            );
 9513        });
 9514    });
 9515
 9516    let editor2 = cx.new_window_entity(|window, cx| {
 9517        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
 9518    });
 9519
 9520    let mut cx = EditorTestContext::for_editor_in(editor2, &mut cx).await;
 9521    cx.update_editor(|editor, window, cx| {
 9522        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 9523            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)]);
 9524        })
 9525    });
 9526
 9527    cx.assert_editor_state(indoc! { "
 9528        fn a() {
 9529              // what
 9530              // a
 9531        ˇ      // long
 9532              // method"});
 9533
 9534    cx.update_editor(|editor, window, cx| {
 9535        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9536    });
 9537
 9538    // Although we could potentially make the action work when the syntax node
 9539    // is half-hidden, it seems a bit dangerous as you can't easily tell what it
 9540    // did. Maybe we could also expand the excerpt to contain the range?
 9541    cx.assert_editor_state(indoc! { "
 9542        fn a() {
 9543              // what
 9544              // a
 9545        ˇ      // long
 9546              // method"});
 9547}
 9548
 9549#[gpui::test]
 9550async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 9551    init_test(cx, |_| {});
 9552
 9553    let base_text = r#"
 9554        impl A {
 9555            // this is an uncommitted comment
 9556
 9557            fn b() {
 9558                c();
 9559            }
 9560
 9561            // this is another uncommitted comment
 9562
 9563            fn d() {
 9564                // e
 9565                // f
 9566            }
 9567        }
 9568
 9569        fn g() {
 9570            // h
 9571        }
 9572    "#
 9573    .unindent();
 9574
 9575    let text = r#"
 9576        ˇimpl A {
 9577
 9578            fn b() {
 9579                c();
 9580            }
 9581
 9582            fn d() {
 9583                // e
 9584                // f
 9585            }
 9586        }
 9587
 9588        fn g() {
 9589            // h
 9590        }
 9591    "#
 9592    .unindent();
 9593
 9594    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9595    cx.set_state(&text);
 9596    cx.set_head_text(&base_text);
 9597    cx.update_editor(|editor, window, cx| {
 9598        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 9599    });
 9600
 9601    cx.assert_state_with_diff(
 9602        "
 9603        ˇimpl A {
 9604      -     // this is an uncommitted comment
 9605
 9606            fn b() {
 9607                c();
 9608            }
 9609
 9610      -     // this is another uncommitted comment
 9611      -
 9612            fn d() {
 9613                // e
 9614                // f
 9615            }
 9616        }
 9617
 9618        fn g() {
 9619            // h
 9620        }
 9621    "
 9622        .unindent(),
 9623    );
 9624
 9625    let expected_display_text = "
 9626        impl A {
 9627            // this is an uncommitted comment
 9628
 9629            fn b() {
 9630 9631            }
 9632
 9633            // this is another uncommitted comment
 9634
 9635            fn d() {
 9636 9637            }
 9638        }
 9639
 9640        fn g() {
 9641 9642        }
 9643        "
 9644    .unindent();
 9645
 9646    cx.update_editor(|editor, window, cx| {
 9647        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 9648        assert_eq!(editor.display_text(cx), expected_display_text);
 9649    });
 9650}
 9651
 9652#[gpui::test]
 9653async fn test_autoindent(cx: &mut TestAppContext) {
 9654    init_test(cx, |_| {});
 9655
 9656    let language = Arc::new(
 9657        Language::new(
 9658            LanguageConfig {
 9659                brackets: BracketPairConfig {
 9660                    pairs: vec![
 9661                        BracketPair {
 9662                            start: "{".to_string(),
 9663                            end: "}".to_string(),
 9664                            close: false,
 9665                            surround: false,
 9666                            newline: true,
 9667                        },
 9668                        BracketPair {
 9669                            start: "(".to_string(),
 9670                            end: ")".to_string(),
 9671                            close: false,
 9672                            surround: false,
 9673                            newline: true,
 9674                        },
 9675                    ],
 9676                    ..Default::default()
 9677                },
 9678                ..Default::default()
 9679            },
 9680            Some(tree_sitter_rust::LANGUAGE.into()),
 9681        )
 9682        .with_indents_query(
 9683            r#"
 9684                (_ "(" ")" @end) @indent
 9685                (_ "{" "}" @end) @indent
 9686            "#,
 9687        )
 9688        .unwrap(),
 9689    );
 9690
 9691    let text = "fn a() {}";
 9692
 9693    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9694    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9695    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9696    editor
 9697        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9698        .await;
 9699
 9700    editor.update_in(cx, |editor, window, cx| {
 9701        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9702            s.select_ranges([5..5, 8..8, 9..9])
 9703        });
 9704        editor.newline(&Newline, window, cx);
 9705        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 9706        assert_eq!(
 9707            editor.selections.ranges(&editor.display_snapshot(cx)),
 9708            &[
 9709                Point::new(1, 4)..Point::new(1, 4),
 9710                Point::new(3, 4)..Point::new(3, 4),
 9711                Point::new(5, 0)..Point::new(5, 0)
 9712            ]
 9713        );
 9714    });
 9715}
 9716
 9717#[gpui::test]
 9718async fn test_autoindent_disabled(cx: &mut TestAppContext) {
 9719    init_test(cx, |settings| settings.defaults.auto_indent = Some(false));
 9720
 9721    let language = Arc::new(
 9722        Language::new(
 9723            LanguageConfig {
 9724                brackets: BracketPairConfig {
 9725                    pairs: vec![
 9726                        BracketPair {
 9727                            start: "{".to_string(),
 9728                            end: "}".to_string(),
 9729                            close: false,
 9730                            surround: false,
 9731                            newline: true,
 9732                        },
 9733                        BracketPair {
 9734                            start: "(".to_string(),
 9735                            end: ")".to_string(),
 9736                            close: false,
 9737                            surround: false,
 9738                            newline: true,
 9739                        },
 9740                    ],
 9741                    ..Default::default()
 9742                },
 9743                ..Default::default()
 9744            },
 9745            Some(tree_sitter_rust::LANGUAGE.into()),
 9746        )
 9747        .with_indents_query(
 9748            r#"
 9749                (_ "(" ")" @end) @indent
 9750                (_ "{" "}" @end) @indent
 9751            "#,
 9752        )
 9753        .unwrap(),
 9754    );
 9755
 9756    let text = "fn a() {}";
 9757
 9758    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9759    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9760    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9761    editor
 9762        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9763        .await;
 9764
 9765    editor.update_in(cx, |editor, window, cx| {
 9766        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9767            s.select_ranges([5..5, 8..8, 9..9])
 9768        });
 9769        editor.newline(&Newline, window, cx);
 9770        assert_eq!(
 9771            editor.text(cx),
 9772            indoc!(
 9773                "
 9774                fn a(
 9775
 9776                ) {
 9777
 9778                }
 9779                "
 9780            )
 9781        );
 9782        assert_eq!(
 9783            editor.selections.ranges(&editor.display_snapshot(cx)),
 9784            &[
 9785                Point::new(1, 0)..Point::new(1, 0),
 9786                Point::new(3, 0)..Point::new(3, 0),
 9787                Point::new(5, 0)..Point::new(5, 0)
 9788            ]
 9789        );
 9790    });
 9791}
 9792
 9793#[gpui::test]
 9794async fn test_autoindent_disabled_with_nested_language(cx: &mut TestAppContext) {
 9795    init_test(cx, |settings| {
 9796        settings.defaults.auto_indent = Some(true);
 9797        settings.languages.0.insert(
 9798            "python".into(),
 9799            LanguageSettingsContent {
 9800                auto_indent: Some(false),
 9801                ..Default::default()
 9802            },
 9803        );
 9804    });
 9805
 9806    let mut cx = EditorTestContext::new(cx).await;
 9807
 9808    let injected_language = Arc::new(
 9809        Language::new(
 9810            LanguageConfig {
 9811                brackets: BracketPairConfig {
 9812                    pairs: vec![
 9813                        BracketPair {
 9814                            start: "{".to_string(),
 9815                            end: "}".to_string(),
 9816                            close: false,
 9817                            surround: false,
 9818                            newline: true,
 9819                        },
 9820                        BracketPair {
 9821                            start: "(".to_string(),
 9822                            end: ")".to_string(),
 9823                            close: true,
 9824                            surround: false,
 9825                            newline: true,
 9826                        },
 9827                    ],
 9828                    ..Default::default()
 9829                },
 9830                name: "python".into(),
 9831                ..Default::default()
 9832            },
 9833            Some(tree_sitter_python::LANGUAGE.into()),
 9834        )
 9835        .with_indents_query(
 9836            r#"
 9837                (_ "(" ")" @end) @indent
 9838                (_ "{" "}" @end) @indent
 9839            "#,
 9840        )
 9841        .unwrap(),
 9842    );
 9843
 9844    let language = Arc::new(
 9845        Language::new(
 9846            LanguageConfig {
 9847                brackets: BracketPairConfig {
 9848                    pairs: vec![
 9849                        BracketPair {
 9850                            start: "{".to_string(),
 9851                            end: "}".to_string(),
 9852                            close: false,
 9853                            surround: false,
 9854                            newline: true,
 9855                        },
 9856                        BracketPair {
 9857                            start: "(".to_string(),
 9858                            end: ")".to_string(),
 9859                            close: true,
 9860                            surround: false,
 9861                            newline: true,
 9862                        },
 9863                    ],
 9864                    ..Default::default()
 9865                },
 9866                name: LanguageName::new("rust"),
 9867                ..Default::default()
 9868            },
 9869            Some(tree_sitter_rust::LANGUAGE.into()),
 9870        )
 9871        .with_indents_query(
 9872            r#"
 9873                (_ "(" ")" @end) @indent
 9874                (_ "{" "}" @end) @indent
 9875            "#,
 9876        )
 9877        .unwrap()
 9878        .with_injection_query(
 9879            r#"
 9880            (macro_invocation
 9881                macro: (identifier) @_macro_name
 9882                (token_tree) @injection.content
 9883                (#set! injection.language "python"))
 9884           "#,
 9885        )
 9886        .unwrap(),
 9887    );
 9888
 9889    cx.language_registry().add(injected_language);
 9890    cx.language_registry().add(language.clone());
 9891
 9892    cx.update_buffer(|buffer, cx| {
 9893        buffer.set_language(Some(language), cx);
 9894    });
 9895
 9896    cx.set_state(r#"struct A {ˇ}"#);
 9897
 9898    cx.update_editor(|editor, window, cx| {
 9899        editor.newline(&Default::default(), window, cx);
 9900    });
 9901
 9902    cx.assert_editor_state(indoc!(
 9903        "struct A {
 9904            ˇ
 9905        }"
 9906    ));
 9907
 9908    cx.set_state(r#"select_biased!(ˇ)"#);
 9909
 9910    cx.update_editor(|editor, window, cx| {
 9911        editor.newline(&Default::default(), window, cx);
 9912        editor.handle_input("def ", window, cx);
 9913        editor.handle_input("(", window, cx);
 9914        editor.newline(&Default::default(), window, cx);
 9915        editor.handle_input("a", window, cx);
 9916    });
 9917
 9918    cx.assert_editor_state(indoc!(
 9919        "select_biased!(
 9920        def (
 9921 9922        )
 9923        )"
 9924    ));
 9925}
 9926
 9927#[gpui::test]
 9928async fn test_autoindent_selections(cx: &mut TestAppContext) {
 9929    init_test(cx, |_| {});
 9930
 9931    {
 9932        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9933        cx.set_state(indoc! {"
 9934            impl A {
 9935
 9936                fn b() {}
 9937
 9938            «fn c() {
 9939
 9940            }ˇ»
 9941            }
 9942        "});
 9943
 9944        cx.update_editor(|editor, window, cx| {
 9945            editor.autoindent(&Default::default(), window, cx);
 9946        });
 9947
 9948        cx.assert_editor_state(indoc! {"
 9949            impl A {
 9950
 9951                fn b() {}
 9952
 9953                «fn c() {
 9954
 9955                }ˇ»
 9956            }
 9957        "});
 9958    }
 9959
 9960    {
 9961        let mut cx = EditorTestContext::new_multibuffer(
 9962            cx,
 9963            [indoc! { "
 9964                impl A {
 9965                «
 9966                // a
 9967                fn b(){}
 9968                »
 9969                «
 9970                    }
 9971                    fn c(){}
 9972                »
 9973            "}],
 9974        );
 9975
 9976        let buffer = cx.update_editor(|editor, _, cx| {
 9977            let buffer = editor.buffer().update(cx, |buffer, _| {
 9978                buffer.all_buffers().iter().next().unwrap().clone()
 9979            });
 9980            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 9981            buffer
 9982        });
 9983
 9984        cx.run_until_parked();
 9985        cx.update_editor(|editor, window, cx| {
 9986            editor.select_all(&Default::default(), window, cx);
 9987            editor.autoindent(&Default::default(), window, cx)
 9988        });
 9989        cx.run_until_parked();
 9990
 9991        cx.update(|_, cx| {
 9992            assert_eq!(
 9993                buffer.read(cx).text(),
 9994                indoc! { "
 9995                    impl A {
 9996
 9997                        // a
 9998                        fn b(){}
 9999
10000
10001                    }
10002                    fn c(){}
10003
10004                " }
10005            )
10006        });
10007    }
10008}
10009
10010#[gpui::test]
10011async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
10012    init_test(cx, |_| {});
10013
10014    let mut cx = EditorTestContext::new(cx).await;
10015
10016    let language = Arc::new(Language::new(
10017        LanguageConfig {
10018            brackets: BracketPairConfig {
10019                pairs: vec![
10020                    BracketPair {
10021                        start: "{".to_string(),
10022                        end: "}".to_string(),
10023                        close: true,
10024                        surround: true,
10025                        newline: true,
10026                    },
10027                    BracketPair {
10028                        start: "(".to_string(),
10029                        end: ")".to_string(),
10030                        close: true,
10031                        surround: true,
10032                        newline: true,
10033                    },
10034                    BracketPair {
10035                        start: "/*".to_string(),
10036                        end: " */".to_string(),
10037                        close: true,
10038                        surround: true,
10039                        newline: true,
10040                    },
10041                    BracketPair {
10042                        start: "[".to_string(),
10043                        end: "]".to_string(),
10044                        close: false,
10045                        surround: false,
10046                        newline: true,
10047                    },
10048                    BracketPair {
10049                        start: "\"".to_string(),
10050                        end: "\"".to_string(),
10051                        close: true,
10052                        surround: true,
10053                        newline: false,
10054                    },
10055                    BracketPair {
10056                        start: "<".to_string(),
10057                        end: ">".to_string(),
10058                        close: false,
10059                        surround: true,
10060                        newline: true,
10061                    },
10062                ],
10063                ..Default::default()
10064            },
10065            autoclose_before: "})]".to_string(),
10066            ..Default::default()
10067        },
10068        Some(tree_sitter_rust::LANGUAGE.into()),
10069    ));
10070
10071    cx.language_registry().add(language.clone());
10072    cx.update_buffer(|buffer, cx| {
10073        buffer.set_language(Some(language), cx);
10074    });
10075
10076    cx.set_state(
10077        &r#"
10078            🏀ˇ
10079            εˇ
10080            ❤️ˇ
10081        "#
10082        .unindent(),
10083    );
10084
10085    // autoclose multiple nested brackets at multiple cursors
10086    cx.update_editor(|editor, window, cx| {
10087        editor.handle_input("{", window, cx);
10088        editor.handle_input("{", window, cx);
10089        editor.handle_input("{", window, cx);
10090    });
10091    cx.assert_editor_state(
10092        &"
10093            🏀{{{ˇ}}}
10094            ε{{{ˇ}}}
10095            ❤️{{{ˇ}}}
10096        "
10097        .unindent(),
10098    );
10099
10100    // insert a different closing bracket
10101    cx.update_editor(|editor, window, cx| {
10102        editor.handle_input(")", window, cx);
10103    });
10104    cx.assert_editor_state(
10105        &"
10106            🏀{{{)ˇ}}}
10107            ε{{{)ˇ}}}
10108            ❤️{{{)ˇ}}}
10109        "
10110        .unindent(),
10111    );
10112
10113    // skip over the auto-closed brackets when typing a closing bracket
10114    cx.update_editor(|editor, window, cx| {
10115        editor.move_right(&MoveRight, window, cx);
10116        editor.handle_input("}", window, cx);
10117        editor.handle_input("}", window, cx);
10118        editor.handle_input("}", window, cx);
10119    });
10120    cx.assert_editor_state(
10121        &"
10122            🏀{{{)}}}}ˇ
10123            ε{{{)}}}}ˇ
10124            ❤️{{{)}}}}ˇ
10125        "
10126        .unindent(),
10127    );
10128
10129    // autoclose multi-character pairs
10130    cx.set_state(
10131        &"
10132            ˇ
10133            ˇ
10134        "
10135        .unindent(),
10136    );
10137    cx.update_editor(|editor, window, cx| {
10138        editor.handle_input("/", window, cx);
10139        editor.handle_input("*", window, cx);
10140    });
10141    cx.assert_editor_state(
10142        &"
10143            /*ˇ */
10144            /*ˇ */
10145        "
10146        .unindent(),
10147    );
10148
10149    // one cursor autocloses a multi-character pair, one cursor
10150    // does not autoclose.
10151    cx.set_state(
10152        &"
1015310154            ˇ
10155        "
10156        .unindent(),
10157    );
10158    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
10159    cx.assert_editor_state(
10160        &"
10161            /*ˇ */
1016210163        "
10164        .unindent(),
10165    );
10166
10167    // Don't autoclose if the next character isn't whitespace and isn't
10168    // listed in the language's "autoclose_before" section.
10169    cx.set_state("ˇa b");
10170    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10171    cx.assert_editor_state("{ˇa b");
10172
10173    // Don't autoclose if `close` is false for the bracket pair
10174    cx.set_state("ˇ");
10175    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
10176    cx.assert_editor_state("");
10177
10178    // Surround with brackets if text is selected
10179    cx.set_state("«aˇ» b");
10180    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10181    cx.assert_editor_state("{«aˇ»} b");
10182
10183    // Autoclose when not immediately after a word character
10184    cx.set_state("a ˇ");
10185    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10186    cx.assert_editor_state("a \"ˇ\"");
10187
10188    // Autoclose pair where the start and end characters are the same
10189    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10190    cx.assert_editor_state("a \"\"ˇ");
10191
10192    // Don't autoclose when immediately after a word character
10193    cx.set_state("");
10194    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10195    cx.assert_editor_state("a\"ˇ");
10196
10197    // Do autoclose when after a non-word character
10198    cx.set_state("");
10199    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10200    cx.assert_editor_state("{\"ˇ\"");
10201
10202    // Non identical pairs autoclose regardless of preceding character
10203    cx.set_state("");
10204    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10205    cx.assert_editor_state("a{ˇ}");
10206
10207    // Don't autoclose pair if autoclose is disabled
10208    cx.set_state("ˇ");
10209    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
10210    cx.assert_editor_state("");
10211
10212    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
10213    cx.set_state("«aˇ» b");
10214    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
10215    cx.assert_editor_state("<«aˇ»> b");
10216}
10217
10218#[gpui::test]
10219async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
10220    init_test(cx, |settings| {
10221        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
10222    });
10223
10224    let mut cx = EditorTestContext::new(cx).await;
10225
10226    let language = Arc::new(Language::new(
10227        LanguageConfig {
10228            brackets: BracketPairConfig {
10229                pairs: vec![
10230                    BracketPair {
10231                        start: "{".to_string(),
10232                        end: "}".to_string(),
10233                        close: true,
10234                        surround: true,
10235                        newline: true,
10236                    },
10237                    BracketPair {
10238                        start: "(".to_string(),
10239                        end: ")".to_string(),
10240                        close: true,
10241                        surround: true,
10242                        newline: true,
10243                    },
10244                    BracketPair {
10245                        start: "[".to_string(),
10246                        end: "]".to_string(),
10247                        close: false,
10248                        surround: false,
10249                        newline: true,
10250                    },
10251                ],
10252                ..Default::default()
10253            },
10254            autoclose_before: "})]".to_string(),
10255            ..Default::default()
10256        },
10257        Some(tree_sitter_rust::LANGUAGE.into()),
10258    ));
10259
10260    cx.language_registry().add(language.clone());
10261    cx.update_buffer(|buffer, cx| {
10262        buffer.set_language(Some(language), cx);
10263    });
10264
10265    cx.set_state(
10266        &"
10267            ˇ
10268            ˇ
10269            ˇ
10270        "
10271        .unindent(),
10272    );
10273
10274    // ensure only matching closing brackets are skipped over
10275    cx.update_editor(|editor, window, cx| {
10276        editor.handle_input("}", window, cx);
10277        editor.move_left(&MoveLeft, window, cx);
10278        editor.handle_input(")", window, cx);
10279        editor.move_left(&MoveLeft, window, cx);
10280    });
10281    cx.assert_editor_state(
10282        &"
10283            ˇ)}
10284            ˇ)}
10285            ˇ)}
10286        "
10287        .unindent(),
10288    );
10289
10290    // skip-over closing brackets at multiple cursors
10291    cx.update_editor(|editor, window, cx| {
10292        editor.handle_input(")", window, cx);
10293        editor.handle_input("}", window, cx);
10294    });
10295    cx.assert_editor_state(
10296        &"
10297            )}ˇ
10298            )}ˇ
10299            )}ˇ
10300        "
10301        .unindent(),
10302    );
10303
10304    // ignore non-close brackets
10305    cx.update_editor(|editor, window, cx| {
10306        editor.handle_input("]", window, cx);
10307        editor.move_left(&MoveLeft, window, cx);
10308        editor.handle_input("]", window, cx);
10309    });
10310    cx.assert_editor_state(
10311        &"
10312            )}]ˇ]
10313            )}]ˇ]
10314            )}]ˇ]
10315        "
10316        .unindent(),
10317    );
10318}
10319
10320#[gpui::test]
10321async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
10322    init_test(cx, |_| {});
10323
10324    let mut cx = EditorTestContext::new(cx).await;
10325
10326    let html_language = Arc::new(
10327        Language::new(
10328            LanguageConfig {
10329                name: "HTML".into(),
10330                brackets: BracketPairConfig {
10331                    pairs: vec![
10332                        BracketPair {
10333                            start: "<".into(),
10334                            end: ">".into(),
10335                            close: true,
10336                            ..Default::default()
10337                        },
10338                        BracketPair {
10339                            start: "{".into(),
10340                            end: "}".into(),
10341                            close: true,
10342                            ..Default::default()
10343                        },
10344                        BracketPair {
10345                            start: "(".into(),
10346                            end: ")".into(),
10347                            close: true,
10348                            ..Default::default()
10349                        },
10350                    ],
10351                    ..Default::default()
10352                },
10353                autoclose_before: "})]>".into(),
10354                ..Default::default()
10355            },
10356            Some(tree_sitter_html::LANGUAGE.into()),
10357        )
10358        .with_injection_query(
10359            r#"
10360            (script_element
10361                (raw_text) @injection.content
10362                (#set! injection.language "javascript"))
10363            "#,
10364        )
10365        .unwrap(),
10366    );
10367
10368    let javascript_language = Arc::new(Language::new(
10369        LanguageConfig {
10370            name: "JavaScript".into(),
10371            brackets: BracketPairConfig {
10372                pairs: vec![
10373                    BracketPair {
10374                        start: "/*".into(),
10375                        end: " */".into(),
10376                        close: true,
10377                        ..Default::default()
10378                    },
10379                    BracketPair {
10380                        start: "{".into(),
10381                        end: "}".into(),
10382                        close: true,
10383                        ..Default::default()
10384                    },
10385                    BracketPair {
10386                        start: "(".into(),
10387                        end: ")".into(),
10388                        close: true,
10389                        ..Default::default()
10390                    },
10391                ],
10392                ..Default::default()
10393            },
10394            autoclose_before: "})]>".into(),
10395            ..Default::default()
10396        },
10397        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10398    ));
10399
10400    cx.language_registry().add(html_language.clone());
10401    cx.language_registry().add(javascript_language);
10402    cx.executor().run_until_parked();
10403
10404    cx.update_buffer(|buffer, cx| {
10405        buffer.set_language(Some(html_language), cx);
10406    });
10407
10408    cx.set_state(
10409        &r#"
10410            <body>ˇ
10411                <script>
10412                    var x = 1;ˇ
10413                </script>
10414            </body>ˇ
10415        "#
10416        .unindent(),
10417    );
10418
10419    // Precondition: different languages are active at different locations.
10420    cx.update_editor(|editor, window, cx| {
10421        let snapshot = editor.snapshot(window, cx);
10422        let cursors = editor
10423            .selections
10424            .ranges::<usize>(&editor.display_snapshot(cx));
10425        let languages = cursors
10426            .iter()
10427            .map(|c| snapshot.language_at(c.start).unwrap().name())
10428            .collect::<Vec<_>>();
10429        assert_eq!(
10430            languages,
10431            &["HTML".into(), "JavaScript".into(), "HTML".into()]
10432        );
10433    });
10434
10435    // Angle brackets autoclose in HTML, but not JavaScript.
10436    cx.update_editor(|editor, window, cx| {
10437        editor.handle_input("<", window, cx);
10438        editor.handle_input("a", window, cx);
10439    });
10440    cx.assert_editor_state(
10441        &r#"
10442            <body><aˇ>
10443                <script>
10444                    var x = 1;<aˇ
10445                </script>
10446            </body><aˇ>
10447        "#
10448        .unindent(),
10449    );
10450
10451    // Curly braces and parens autoclose in both HTML and JavaScript.
10452    cx.update_editor(|editor, window, cx| {
10453        editor.handle_input(" b=", window, cx);
10454        editor.handle_input("{", window, cx);
10455        editor.handle_input("c", window, cx);
10456        editor.handle_input("(", window, cx);
10457    });
10458    cx.assert_editor_state(
10459        &r#"
10460            <body><a b={c(ˇ)}>
10461                <script>
10462                    var x = 1;<a b={c(ˇ)}
10463                </script>
10464            </body><a b={c(ˇ)}>
10465        "#
10466        .unindent(),
10467    );
10468
10469    // Brackets that were already autoclosed are skipped.
10470    cx.update_editor(|editor, window, cx| {
10471        editor.handle_input(")", window, cx);
10472        editor.handle_input("d", window, cx);
10473        editor.handle_input("}", window, cx);
10474    });
10475    cx.assert_editor_state(
10476        &r#"
10477            <body><a b={c()d}ˇ>
10478                <script>
10479                    var x = 1;<a b={c()d}ˇ
10480                </script>
10481            </body><a b={c()d}ˇ>
10482        "#
10483        .unindent(),
10484    );
10485    cx.update_editor(|editor, window, cx| {
10486        editor.handle_input(">", window, cx);
10487    });
10488    cx.assert_editor_state(
10489        &r#"
10490            <body><a b={c()d}>ˇ
10491                <script>
10492                    var x = 1;<a b={c()d}>ˇ
10493                </script>
10494            </body><a b={c()d}>ˇ
10495        "#
10496        .unindent(),
10497    );
10498
10499    // Reset
10500    cx.set_state(
10501        &r#"
10502            <body>ˇ
10503                <script>
10504                    var x = 1;ˇ
10505                </script>
10506            </body>ˇ
10507        "#
10508        .unindent(),
10509    );
10510
10511    cx.update_editor(|editor, window, cx| {
10512        editor.handle_input("<", window, cx);
10513    });
10514    cx.assert_editor_state(
10515        &r#"
10516            <body><ˇ>
10517                <script>
10518                    var x = 1;<ˇ
10519                </script>
10520            </body><ˇ>
10521        "#
10522        .unindent(),
10523    );
10524
10525    // When backspacing, the closing angle brackets are removed.
10526    cx.update_editor(|editor, window, cx| {
10527        editor.backspace(&Backspace, window, cx);
10528    });
10529    cx.assert_editor_state(
10530        &r#"
10531            <body>ˇ
10532                <script>
10533                    var x = 1;ˇ
10534                </script>
10535            </body>ˇ
10536        "#
10537        .unindent(),
10538    );
10539
10540    // Block comments autoclose in JavaScript, but not HTML.
10541    cx.update_editor(|editor, window, cx| {
10542        editor.handle_input("/", window, cx);
10543        editor.handle_input("*", window, cx);
10544    });
10545    cx.assert_editor_state(
10546        &r#"
10547            <body>/*ˇ
10548                <script>
10549                    var x = 1;/*ˇ */
10550                </script>
10551            </body>/*ˇ
10552        "#
10553        .unindent(),
10554    );
10555}
10556
10557#[gpui::test]
10558async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
10559    init_test(cx, |_| {});
10560
10561    let mut cx = EditorTestContext::new(cx).await;
10562
10563    let rust_language = Arc::new(
10564        Language::new(
10565            LanguageConfig {
10566                name: "Rust".into(),
10567                brackets: serde_json::from_value(json!([
10568                    { "start": "{", "end": "}", "close": true, "newline": true },
10569                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
10570                ]))
10571                .unwrap(),
10572                autoclose_before: "})]>".into(),
10573                ..Default::default()
10574            },
10575            Some(tree_sitter_rust::LANGUAGE.into()),
10576        )
10577        .with_override_query("(string_literal) @string")
10578        .unwrap(),
10579    );
10580
10581    cx.language_registry().add(rust_language.clone());
10582    cx.update_buffer(|buffer, cx| {
10583        buffer.set_language(Some(rust_language), cx);
10584    });
10585
10586    cx.set_state(
10587        &r#"
10588            let x = ˇ
10589        "#
10590        .unindent(),
10591    );
10592
10593    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
10594    cx.update_editor(|editor, window, cx| {
10595        editor.handle_input("\"", window, cx);
10596    });
10597    cx.assert_editor_state(
10598        &r#"
10599            let x = "ˇ"
10600        "#
10601        .unindent(),
10602    );
10603
10604    // Inserting another quotation mark. The cursor moves across the existing
10605    // automatically-inserted quotation mark.
10606    cx.update_editor(|editor, window, cx| {
10607        editor.handle_input("\"", window, cx);
10608    });
10609    cx.assert_editor_state(
10610        &r#"
10611            let x = ""ˇ
10612        "#
10613        .unindent(),
10614    );
10615
10616    // Reset
10617    cx.set_state(
10618        &r#"
10619            let x = ˇ
10620        "#
10621        .unindent(),
10622    );
10623
10624    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
10625    cx.update_editor(|editor, window, cx| {
10626        editor.handle_input("\"", window, cx);
10627        editor.handle_input(" ", window, cx);
10628        editor.move_left(&Default::default(), window, cx);
10629        editor.handle_input("\\", window, cx);
10630        editor.handle_input("\"", window, cx);
10631    });
10632    cx.assert_editor_state(
10633        &r#"
10634            let x = "\"ˇ "
10635        "#
10636        .unindent(),
10637    );
10638
10639    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
10640    // mark. Nothing is inserted.
10641    cx.update_editor(|editor, window, cx| {
10642        editor.move_right(&Default::default(), window, cx);
10643        editor.handle_input("\"", window, cx);
10644    });
10645    cx.assert_editor_state(
10646        &r#"
10647            let x = "\" "ˇ
10648        "#
10649        .unindent(),
10650    );
10651}
10652
10653#[gpui::test]
10654async fn test_surround_with_pair(cx: &mut TestAppContext) {
10655    init_test(cx, |_| {});
10656
10657    let language = Arc::new(Language::new(
10658        LanguageConfig {
10659            brackets: BracketPairConfig {
10660                pairs: vec![
10661                    BracketPair {
10662                        start: "{".to_string(),
10663                        end: "}".to_string(),
10664                        close: true,
10665                        surround: true,
10666                        newline: true,
10667                    },
10668                    BracketPair {
10669                        start: "/* ".to_string(),
10670                        end: "*/".to_string(),
10671                        close: true,
10672                        surround: true,
10673                        ..Default::default()
10674                    },
10675                ],
10676                ..Default::default()
10677            },
10678            ..Default::default()
10679        },
10680        Some(tree_sitter_rust::LANGUAGE.into()),
10681    ));
10682
10683    let text = r#"
10684        a
10685        b
10686        c
10687    "#
10688    .unindent();
10689
10690    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10691    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10692    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10693    editor
10694        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10695        .await;
10696
10697    editor.update_in(cx, |editor, window, cx| {
10698        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10699            s.select_display_ranges([
10700                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10701                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10702                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
10703            ])
10704        });
10705
10706        editor.handle_input("{", window, cx);
10707        editor.handle_input("{", window, cx);
10708        editor.handle_input("{", window, cx);
10709        assert_eq!(
10710            editor.text(cx),
10711            "
10712                {{{a}}}
10713                {{{b}}}
10714                {{{c}}}
10715            "
10716            .unindent()
10717        );
10718        assert_eq!(
10719            editor.selections.display_ranges(cx),
10720            [
10721                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
10722                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
10723                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
10724            ]
10725        );
10726
10727        editor.undo(&Undo, window, cx);
10728        editor.undo(&Undo, window, cx);
10729        editor.undo(&Undo, window, cx);
10730        assert_eq!(
10731            editor.text(cx),
10732            "
10733                a
10734                b
10735                c
10736            "
10737            .unindent()
10738        );
10739        assert_eq!(
10740            editor.selections.display_ranges(cx),
10741            [
10742                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10743                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10744                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10745            ]
10746        );
10747
10748        // Ensure inserting the first character of a multi-byte bracket pair
10749        // doesn't surround the selections with the bracket.
10750        editor.handle_input("/", window, cx);
10751        assert_eq!(
10752            editor.text(cx),
10753            "
10754                /
10755                /
10756                /
10757            "
10758            .unindent()
10759        );
10760        assert_eq!(
10761            editor.selections.display_ranges(cx),
10762            [
10763                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10764                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10765                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10766            ]
10767        );
10768
10769        editor.undo(&Undo, window, cx);
10770        assert_eq!(
10771            editor.text(cx),
10772            "
10773                a
10774                b
10775                c
10776            "
10777            .unindent()
10778        );
10779        assert_eq!(
10780            editor.selections.display_ranges(cx),
10781            [
10782                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10783                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10784                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10785            ]
10786        );
10787
10788        // Ensure inserting the last character of a multi-byte bracket pair
10789        // doesn't surround the selections with the bracket.
10790        editor.handle_input("*", window, cx);
10791        assert_eq!(
10792            editor.text(cx),
10793            "
10794                *
10795                *
10796                *
10797            "
10798            .unindent()
10799        );
10800        assert_eq!(
10801            editor.selections.display_ranges(cx),
10802            [
10803                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10804                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10805                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10806            ]
10807        );
10808    });
10809}
10810
10811#[gpui::test]
10812async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
10813    init_test(cx, |_| {});
10814
10815    let language = Arc::new(Language::new(
10816        LanguageConfig {
10817            brackets: BracketPairConfig {
10818                pairs: vec![BracketPair {
10819                    start: "{".to_string(),
10820                    end: "}".to_string(),
10821                    close: true,
10822                    surround: true,
10823                    newline: true,
10824                }],
10825                ..Default::default()
10826            },
10827            autoclose_before: "}".to_string(),
10828            ..Default::default()
10829        },
10830        Some(tree_sitter_rust::LANGUAGE.into()),
10831    ));
10832
10833    let text = r#"
10834        a
10835        b
10836        c
10837    "#
10838    .unindent();
10839
10840    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10841    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10842    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10843    editor
10844        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10845        .await;
10846
10847    editor.update_in(cx, |editor, window, cx| {
10848        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10849            s.select_ranges([
10850                Point::new(0, 1)..Point::new(0, 1),
10851                Point::new(1, 1)..Point::new(1, 1),
10852                Point::new(2, 1)..Point::new(2, 1),
10853            ])
10854        });
10855
10856        editor.handle_input("{", window, cx);
10857        editor.handle_input("{", window, cx);
10858        editor.handle_input("_", window, cx);
10859        assert_eq!(
10860            editor.text(cx),
10861            "
10862                a{{_}}
10863                b{{_}}
10864                c{{_}}
10865            "
10866            .unindent()
10867        );
10868        assert_eq!(
10869            editor
10870                .selections
10871                .ranges::<Point>(&editor.display_snapshot(cx)),
10872            [
10873                Point::new(0, 4)..Point::new(0, 4),
10874                Point::new(1, 4)..Point::new(1, 4),
10875                Point::new(2, 4)..Point::new(2, 4)
10876            ]
10877        );
10878
10879        editor.backspace(&Default::default(), window, cx);
10880        editor.backspace(&Default::default(), window, cx);
10881        assert_eq!(
10882            editor.text(cx),
10883            "
10884                a{}
10885                b{}
10886                c{}
10887            "
10888            .unindent()
10889        );
10890        assert_eq!(
10891            editor
10892                .selections
10893                .ranges::<Point>(&editor.display_snapshot(cx)),
10894            [
10895                Point::new(0, 2)..Point::new(0, 2),
10896                Point::new(1, 2)..Point::new(1, 2),
10897                Point::new(2, 2)..Point::new(2, 2)
10898            ]
10899        );
10900
10901        editor.delete_to_previous_word_start(&Default::default(), window, cx);
10902        assert_eq!(
10903            editor.text(cx),
10904            "
10905                a
10906                b
10907                c
10908            "
10909            .unindent()
10910        );
10911        assert_eq!(
10912            editor
10913                .selections
10914                .ranges::<Point>(&editor.display_snapshot(cx)),
10915            [
10916                Point::new(0, 1)..Point::new(0, 1),
10917                Point::new(1, 1)..Point::new(1, 1),
10918                Point::new(2, 1)..Point::new(2, 1)
10919            ]
10920        );
10921    });
10922}
10923
10924#[gpui::test]
10925async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
10926    init_test(cx, |settings| {
10927        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
10928    });
10929
10930    let mut cx = EditorTestContext::new(cx).await;
10931
10932    let language = Arc::new(Language::new(
10933        LanguageConfig {
10934            brackets: BracketPairConfig {
10935                pairs: vec![
10936                    BracketPair {
10937                        start: "{".to_string(),
10938                        end: "}".to_string(),
10939                        close: true,
10940                        surround: true,
10941                        newline: true,
10942                    },
10943                    BracketPair {
10944                        start: "(".to_string(),
10945                        end: ")".to_string(),
10946                        close: true,
10947                        surround: true,
10948                        newline: true,
10949                    },
10950                    BracketPair {
10951                        start: "[".to_string(),
10952                        end: "]".to_string(),
10953                        close: false,
10954                        surround: true,
10955                        newline: true,
10956                    },
10957                ],
10958                ..Default::default()
10959            },
10960            autoclose_before: "})]".to_string(),
10961            ..Default::default()
10962        },
10963        Some(tree_sitter_rust::LANGUAGE.into()),
10964    ));
10965
10966    cx.language_registry().add(language.clone());
10967    cx.update_buffer(|buffer, cx| {
10968        buffer.set_language(Some(language), cx);
10969    });
10970
10971    cx.set_state(
10972        &"
10973            {(ˇ)}
10974            [[ˇ]]
10975            {(ˇ)}
10976        "
10977        .unindent(),
10978    );
10979
10980    cx.update_editor(|editor, window, cx| {
10981        editor.backspace(&Default::default(), window, cx);
10982        editor.backspace(&Default::default(), window, cx);
10983    });
10984
10985    cx.assert_editor_state(
10986        &"
10987            ˇ
10988            ˇ]]
10989            ˇ
10990        "
10991        .unindent(),
10992    );
10993
10994    cx.update_editor(|editor, window, cx| {
10995        editor.handle_input("{", window, cx);
10996        editor.handle_input("{", window, cx);
10997        editor.move_right(&MoveRight, window, cx);
10998        editor.move_right(&MoveRight, window, cx);
10999        editor.move_left(&MoveLeft, window, cx);
11000        editor.move_left(&MoveLeft, window, cx);
11001        editor.backspace(&Default::default(), window, cx);
11002    });
11003
11004    cx.assert_editor_state(
11005        &"
11006            {ˇ}
11007            {ˇ}]]
11008            {ˇ}
11009        "
11010        .unindent(),
11011    );
11012
11013    cx.update_editor(|editor, window, cx| {
11014        editor.backspace(&Default::default(), window, cx);
11015    });
11016
11017    cx.assert_editor_state(
11018        &"
11019            ˇ
11020            ˇ]]
11021            ˇ
11022        "
11023        .unindent(),
11024    );
11025}
11026
11027#[gpui::test]
11028async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
11029    init_test(cx, |_| {});
11030
11031    let language = Arc::new(Language::new(
11032        LanguageConfig::default(),
11033        Some(tree_sitter_rust::LANGUAGE.into()),
11034    ));
11035
11036    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
11037    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11038    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11039    editor
11040        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
11041        .await;
11042
11043    editor.update_in(cx, |editor, window, cx| {
11044        editor.set_auto_replace_emoji_shortcode(true);
11045
11046        editor.handle_input("Hello ", window, cx);
11047        editor.handle_input(":wave", window, cx);
11048        assert_eq!(editor.text(cx), "Hello :wave".unindent());
11049
11050        editor.handle_input(":", window, cx);
11051        assert_eq!(editor.text(cx), "Hello 👋".unindent());
11052
11053        editor.handle_input(" :smile", window, cx);
11054        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
11055
11056        editor.handle_input(":", window, cx);
11057        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
11058
11059        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
11060        editor.handle_input(":wave", window, cx);
11061        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
11062
11063        editor.handle_input(":", window, cx);
11064        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
11065
11066        editor.handle_input(":1", window, cx);
11067        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
11068
11069        editor.handle_input(":", window, cx);
11070        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
11071
11072        // Ensure shortcode does not get replaced when it is part of a word
11073        editor.handle_input(" Test:wave", window, cx);
11074        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
11075
11076        editor.handle_input(":", window, cx);
11077        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
11078
11079        editor.set_auto_replace_emoji_shortcode(false);
11080
11081        // Ensure shortcode does not get replaced when auto replace is off
11082        editor.handle_input(" :wave", window, cx);
11083        assert_eq!(
11084            editor.text(cx),
11085            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
11086        );
11087
11088        editor.handle_input(":", window, cx);
11089        assert_eq!(
11090            editor.text(cx),
11091            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
11092        );
11093    });
11094}
11095
11096#[gpui::test]
11097async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
11098    init_test(cx, |_| {});
11099
11100    let (text, insertion_ranges) = marked_text_ranges(
11101        indoc! {"
11102            ˇ
11103        "},
11104        false,
11105    );
11106
11107    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
11108    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11109
11110    _ = editor.update_in(cx, |editor, window, cx| {
11111        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
11112
11113        editor
11114            .insert_snippet(&insertion_ranges, snippet, window, cx)
11115            .unwrap();
11116
11117        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
11118            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
11119            assert_eq!(editor.text(cx), expected_text);
11120            assert_eq!(
11121                editor
11122                    .selections
11123                    .ranges::<usize>(&editor.display_snapshot(cx)),
11124                selection_ranges
11125            );
11126        }
11127
11128        assert(
11129            editor,
11130            cx,
11131            indoc! {"
11132            type «» =•
11133            "},
11134        );
11135
11136        assert!(editor.context_menu_visible(), "There should be a matches");
11137    });
11138}
11139
11140#[gpui::test]
11141async fn test_snippet_tabstop_navigation_with_placeholders(cx: &mut TestAppContext) {
11142    init_test(cx, |_| {});
11143
11144    fn assert_state(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
11145        let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
11146        assert_eq!(editor.text(cx), expected_text);
11147        assert_eq!(
11148            editor
11149                .selections
11150                .ranges::<usize>(&editor.display_snapshot(cx)),
11151            selection_ranges
11152        );
11153    }
11154
11155    let (text, insertion_ranges) = marked_text_ranges(
11156        indoc! {"
11157            ˇ
11158        "},
11159        false,
11160    );
11161
11162    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
11163    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11164
11165    _ = editor.update_in(cx, |editor, window, cx| {
11166        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2; $3").unwrap();
11167
11168        editor
11169            .insert_snippet(&insertion_ranges, snippet, window, cx)
11170            .unwrap();
11171
11172        assert_state(
11173            editor,
11174            cx,
11175            indoc! {"
11176            type «» = ;•
11177            "},
11178        );
11179
11180        assert!(
11181            editor.context_menu_visible(),
11182            "Context menu should be visible for placeholder choices"
11183        );
11184
11185        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11186
11187        assert_state(
11188            editor,
11189            cx,
11190            indoc! {"
11191            type  = «»;•
11192            "},
11193        );
11194
11195        assert!(
11196            !editor.context_menu_visible(),
11197            "Context menu should be hidden after moving to next tabstop"
11198        );
11199
11200        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11201
11202        assert_state(
11203            editor,
11204            cx,
11205            indoc! {"
11206            type  = ; ˇ
11207            "},
11208        );
11209
11210        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11211
11212        assert_state(
11213            editor,
11214            cx,
11215            indoc! {"
11216            type  = ; ˇ
11217            "},
11218        );
11219    });
11220
11221    _ = editor.update_in(cx, |editor, window, cx| {
11222        editor.select_all(&SelectAll, window, cx);
11223        editor.backspace(&Backspace, window, cx);
11224
11225        let snippet = Snippet::parse("fn ${1|,foo,bar|} = ${2:value}; $3").unwrap();
11226        let insertion_ranges = editor
11227            .selections
11228            .all(&editor.display_snapshot(cx))
11229            .iter()
11230            .map(|s| s.range())
11231            .collect::<Vec<_>>();
11232
11233        editor
11234            .insert_snippet(&insertion_ranges, snippet, window, cx)
11235            .unwrap();
11236
11237        assert_state(editor, cx, "fn «» = value;•");
11238
11239        assert!(
11240            editor.context_menu_visible(),
11241            "Context menu should be visible for placeholder choices"
11242        );
11243
11244        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11245
11246        assert_state(editor, cx, "fn  = «valueˇ»;•");
11247
11248        editor.previous_snippet_tabstop(&PreviousSnippetTabstop, window, cx);
11249
11250        assert_state(editor, cx, "fn «» = value;•");
11251
11252        assert!(
11253            editor.context_menu_visible(),
11254            "Context menu should be visible again after returning to first tabstop"
11255        );
11256
11257        editor.previous_snippet_tabstop(&PreviousSnippetTabstop, window, cx);
11258
11259        assert_state(editor, cx, "fn «» = value;•");
11260    });
11261}
11262
11263#[gpui::test]
11264async fn test_snippets(cx: &mut TestAppContext) {
11265    init_test(cx, |_| {});
11266
11267    let mut cx = EditorTestContext::new(cx).await;
11268
11269    cx.set_state(indoc! {"
11270        a.ˇ b
11271        a.ˇ b
11272        a.ˇ b
11273    "});
11274
11275    cx.update_editor(|editor, window, cx| {
11276        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
11277        let insertion_ranges = editor
11278            .selections
11279            .all(&editor.display_snapshot(cx))
11280            .iter()
11281            .map(|s| s.range())
11282            .collect::<Vec<_>>();
11283        editor
11284            .insert_snippet(&insertion_ranges, snippet, window, cx)
11285            .unwrap();
11286    });
11287
11288    cx.assert_editor_state(indoc! {"
11289        a.f(«oneˇ», two, «threeˇ») b
11290        a.f(«oneˇ», two, «threeˇ») b
11291        a.f(«oneˇ», two, «threeˇ») b
11292    "});
11293
11294    // Can't move earlier than the first tab stop
11295    cx.update_editor(|editor, window, cx| {
11296        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
11297    });
11298    cx.assert_editor_state(indoc! {"
11299        a.f(«oneˇ», two, «threeˇ») b
11300        a.f(«oneˇ», two, «threeˇ») b
11301        a.f(«oneˇ», two, «threeˇ») b
11302    "});
11303
11304    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11305    cx.assert_editor_state(indoc! {"
11306        a.f(one, «twoˇ», three) b
11307        a.f(one, «twoˇ», three) b
11308        a.f(one, «twoˇ», three) b
11309    "});
11310
11311    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
11312    cx.assert_editor_state(indoc! {"
11313        a.f(«oneˇ», two, «threeˇ») b
11314        a.f(«oneˇ», two, «threeˇ») b
11315        a.f(«oneˇ», two, «threeˇ») b
11316    "});
11317
11318    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11319    cx.assert_editor_state(indoc! {"
11320        a.f(one, «twoˇ», three) b
11321        a.f(one, «twoˇ», three) b
11322        a.f(one, «twoˇ», three) b
11323    "});
11324    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11325    cx.assert_editor_state(indoc! {"
11326        a.f(one, two, three)ˇ b
11327        a.f(one, two, three)ˇ b
11328        a.f(one, two, three)ˇ b
11329    "});
11330
11331    // As soon as the last tab stop is reached, snippet state is gone
11332    cx.update_editor(|editor, window, cx| {
11333        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
11334    });
11335    cx.assert_editor_state(indoc! {"
11336        a.f(one, two, three)ˇ b
11337        a.f(one, two, three)ˇ b
11338        a.f(one, two, three)ˇ b
11339    "});
11340}
11341
11342#[gpui::test]
11343async fn test_snippet_indentation(cx: &mut TestAppContext) {
11344    init_test(cx, |_| {});
11345
11346    let mut cx = EditorTestContext::new(cx).await;
11347
11348    cx.update_editor(|editor, window, cx| {
11349        let snippet = Snippet::parse(indoc! {"
11350            /*
11351             * Multiline comment with leading indentation
11352             *
11353             * $1
11354             */
11355            $0"})
11356        .unwrap();
11357        let insertion_ranges = editor
11358            .selections
11359            .all(&editor.display_snapshot(cx))
11360            .iter()
11361            .map(|s| s.range())
11362            .collect::<Vec<_>>();
11363        editor
11364            .insert_snippet(&insertion_ranges, snippet, window, cx)
11365            .unwrap();
11366    });
11367
11368    cx.assert_editor_state(indoc! {"
11369        /*
11370         * Multiline comment with leading indentation
11371         *
11372         * ˇ
11373         */
11374    "});
11375
11376    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11377    cx.assert_editor_state(indoc! {"
11378        /*
11379         * Multiline comment with leading indentation
11380         *
11381         *•
11382         */
11383        ˇ"});
11384}
11385
11386#[gpui::test]
11387async fn test_document_format_during_save(cx: &mut TestAppContext) {
11388    init_test(cx, |_| {});
11389
11390    let fs = FakeFs::new(cx.executor());
11391    fs.insert_file(path!("/file.rs"), Default::default()).await;
11392
11393    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
11394
11395    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11396    language_registry.add(rust_lang());
11397    let mut fake_servers = language_registry.register_fake_lsp(
11398        "Rust",
11399        FakeLspAdapter {
11400            capabilities: lsp::ServerCapabilities {
11401                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11402                ..Default::default()
11403            },
11404            ..Default::default()
11405        },
11406    );
11407
11408    let buffer = project
11409        .update(cx, |project, cx| {
11410            project.open_local_buffer(path!("/file.rs"), cx)
11411        })
11412        .await
11413        .unwrap();
11414
11415    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11416    let (editor, cx) = cx.add_window_view(|window, cx| {
11417        build_editor_with_project(project.clone(), buffer, window, cx)
11418    });
11419    editor.update_in(cx, |editor, window, cx| {
11420        editor.set_text("one\ntwo\nthree\n", window, cx)
11421    });
11422    assert!(cx.read(|cx| editor.is_dirty(cx)));
11423
11424    cx.executor().start_waiting();
11425    let fake_server = fake_servers.next().await.unwrap();
11426
11427    {
11428        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11429            move |params, _| async move {
11430                assert_eq!(
11431                    params.text_document.uri,
11432                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11433                );
11434                assert_eq!(params.options.tab_size, 4);
11435                Ok(Some(vec![lsp::TextEdit::new(
11436                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11437                    ", ".to_string(),
11438                )]))
11439            },
11440        );
11441        let save = editor
11442            .update_in(cx, |editor, window, cx| {
11443                editor.save(
11444                    SaveOptions {
11445                        format: true,
11446                        autosave: false,
11447                    },
11448                    project.clone(),
11449                    window,
11450                    cx,
11451                )
11452            })
11453            .unwrap();
11454        cx.executor().start_waiting();
11455        save.await;
11456
11457        assert_eq!(
11458            editor.update(cx, |editor, cx| editor.text(cx)),
11459            "one, two\nthree\n"
11460        );
11461        assert!(!cx.read(|cx| editor.is_dirty(cx)));
11462    }
11463
11464    {
11465        editor.update_in(cx, |editor, window, cx| {
11466            editor.set_text("one\ntwo\nthree\n", window, cx)
11467        });
11468        assert!(cx.read(|cx| editor.is_dirty(cx)));
11469
11470        // Ensure we can still save even if formatting hangs.
11471        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11472            move |params, _| async move {
11473                assert_eq!(
11474                    params.text_document.uri,
11475                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11476                );
11477                futures::future::pending::<()>().await;
11478                unreachable!()
11479            },
11480        );
11481        let save = editor
11482            .update_in(cx, |editor, window, cx| {
11483                editor.save(
11484                    SaveOptions {
11485                        format: true,
11486                        autosave: false,
11487                    },
11488                    project.clone(),
11489                    window,
11490                    cx,
11491                )
11492            })
11493            .unwrap();
11494        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11495        cx.executor().start_waiting();
11496        save.await;
11497        assert_eq!(
11498            editor.update(cx, |editor, cx| editor.text(cx)),
11499            "one\ntwo\nthree\n"
11500        );
11501    }
11502
11503    // Set rust language override and assert overridden tabsize is sent to language server
11504    update_test_language_settings(cx, |settings| {
11505        settings.languages.0.insert(
11506            "Rust".into(),
11507            LanguageSettingsContent {
11508                tab_size: NonZeroU32::new(8),
11509                ..Default::default()
11510            },
11511        );
11512    });
11513
11514    {
11515        editor.update_in(cx, |editor, window, cx| {
11516            editor.set_text("somehting_new\n", window, cx)
11517        });
11518        assert!(cx.read(|cx| editor.is_dirty(cx)));
11519        let _formatting_request_signal = fake_server
11520            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
11521                assert_eq!(
11522                    params.text_document.uri,
11523                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11524                );
11525                assert_eq!(params.options.tab_size, 8);
11526                Ok(Some(vec![]))
11527            });
11528        let save = editor
11529            .update_in(cx, |editor, window, cx| {
11530                editor.save(
11531                    SaveOptions {
11532                        format: true,
11533                        autosave: false,
11534                    },
11535                    project.clone(),
11536                    window,
11537                    cx,
11538                )
11539            })
11540            .unwrap();
11541        cx.executor().start_waiting();
11542        save.await;
11543    }
11544}
11545
11546#[gpui::test]
11547async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
11548    init_test(cx, |settings| {
11549        settings.defaults.ensure_final_newline_on_save = Some(false);
11550    });
11551
11552    let fs = FakeFs::new(cx.executor());
11553    fs.insert_file(path!("/file.txt"), "foo".into()).await;
11554
11555    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
11556
11557    let buffer = project
11558        .update(cx, |project, cx| {
11559            project.open_local_buffer(path!("/file.txt"), cx)
11560        })
11561        .await
11562        .unwrap();
11563
11564    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11565    let (editor, cx) = cx.add_window_view(|window, cx| {
11566        build_editor_with_project(project.clone(), buffer, window, cx)
11567    });
11568    editor.update_in(cx, |editor, window, cx| {
11569        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
11570            s.select_ranges([0..0])
11571        });
11572    });
11573    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11574
11575    editor.update_in(cx, |editor, window, cx| {
11576        editor.handle_input("\n", window, cx)
11577    });
11578    cx.run_until_parked();
11579    save(&editor, &project, cx).await;
11580    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11581
11582    editor.update_in(cx, |editor, window, cx| {
11583        editor.undo(&Default::default(), window, cx);
11584    });
11585    save(&editor, &project, cx).await;
11586    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11587
11588    editor.update_in(cx, |editor, window, cx| {
11589        editor.redo(&Default::default(), window, cx);
11590    });
11591    cx.run_until_parked();
11592    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11593
11594    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
11595        let save = editor
11596            .update_in(cx, |editor, window, cx| {
11597                editor.save(
11598                    SaveOptions {
11599                        format: true,
11600                        autosave: false,
11601                    },
11602                    project.clone(),
11603                    window,
11604                    cx,
11605                )
11606            })
11607            .unwrap();
11608        cx.executor().start_waiting();
11609        save.await;
11610        assert!(!cx.read(|cx| editor.is_dirty(cx)));
11611    }
11612}
11613
11614#[gpui::test]
11615async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
11616    init_test(cx, |_| {});
11617
11618    let cols = 4;
11619    let rows = 10;
11620    let sample_text_1 = sample_text(rows, cols, 'a');
11621    assert_eq!(
11622        sample_text_1,
11623        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11624    );
11625    let sample_text_2 = sample_text(rows, cols, 'l');
11626    assert_eq!(
11627        sample_text_2,
11628        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11629    );
11630    let sample_text_3 = sample_text(rows, cols, 'v');
11631    assert_eq!(
11632        sample_text_3,
11633        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11634    );
11635
11636    let fs = FakeFs::new(cx.executor());
11637    fs.insert_tree(
11638        path!("/a"),
11639        json!({
11640            "main.rs": sample_text_1,
11641            "other.rs": sample_text_2,
11642            "lib.rs": sample_text_3,
11643        }),
11644    )
11645    .await;
11646
11647    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11648    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11649    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11650
11651    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11652    language_registry.add(rust_lang());
11653    let mut fake_servers = language_registry.register_fake_lsp(
11654        "Rust",
11655        FakeLspAdapter {
11656            capabilities: lsp::ServerCapabilities {
11657                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11658                ..Default::default()
11659            },
11660            ..Default::default()
11661        },
11662    );
11663
11664    let worktree = project.update(cx, |project, cx| {
11665        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
11666        assert_eq!(worktrees.len(), 1);
11667        worktrees.pop().unwrap()
11668    });
11669    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11670
11671    let buffer_1 = project
11672        .update(cx, |project, cx| {
11673            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
11674        })
11675        .await
11676        .unwrap();
11677    let buffer_2 = project
11678        .update(cx, |project, cx| {
11679            project.open_buffer((worktree_id, rel_path("other.rs")), cx)
11680        })
11681        .await
11682        .unwrap();
11683    let buffer_3 = project
11684        .update(cx, |project, cx| {
11685            project.open_buffer((worktree_id, rel_path("lib.rs")), cx)
11686        })
11687        .await
11688        .unwrap();
11689
11690    let multi_buffer = cx.new(|cx| {
11691        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11692        multi_buffer.push_excerpts(
11693            buffer_1.clone(),
11694            [
11695                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11696                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11697                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11698            ],
11699            cx,
11700        );
11701        multi_buffer.push_excerpts(
11702            buffer_2.clone(),
11703            [
11704                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11705                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11706                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11707            ],
11708            cx,
11709        );
11710        multi_buffer.push_excerpts(
11711            buffer_3.clone(),
11712            [
11713                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11714                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11715                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11716            ],
11717            cx,
11718        );
11719        multi_buffer
11720    });
11721    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
11722        Editor::new(
11723            EditorMode::full(),
11724            multi_buffer,
11725            Some(project.clone()),
11726            window,
11727            cx,
11728        )
11729    });
11730
11731    multi_buffer_editor.update_in(cx, |editor, window, cx| {
11732        editor.change_selections(
11733            SelectionEffects::scroll(Autoscroll::Next),
11734            window,
11735            cx,
11736            |s| s.select_ranges(Some(1..2)),
11737        );
11738        editor.insert("|one|two|three|", window, cx);
11739    });
11740    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
11741    multi_buffer_editor.update_in(cx, |editor, window, cx| {
11742        editor.change_selections(
11743            SelectionEffects::scroll(Autoscroll::Next),
11744            window,
11745            cx,
11746            |s| s.select_ranges(Some(60..70)),
11747        );
11748        editor.insert("|four|five|six|", window, cx);
11749    });
11750    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
11751
11752    // First two buffers should be edited, but not the third one.
11753    assert_eq!(
11754        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11755        "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}",
11756    );
11757    buffer_1.update(cx, |buffer, _| {
11758        assert!(buffer.is_dirty());
11759        assert_eq!(
11760            buffer.text(),
11761            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
11762        )
11763    });
11764    buffer_2.update(cx, |buffer, _| {
11765        assert!(buffer.is_dirty());
11766        assert_eq!(
11767            buffer.text(),
11768            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
11769        )
11770    });
11771    buffer_3.update(cx, |buffer, _| {
11772        assert!(!buffer.is_dirty());
11773        assert_eq!(buffer.text(), sample_text_3,)
11774    });
11775    cx.executor().run_until_parked();
11776
11777    cx.executor().start_waiting();
11778    let save = multi_buffer_editor
11779        .update_in(cx, |editor, window, cx| {
11780            editor.save(
11781                SaveOptions {
11782                    format: true,
11783                    autosave: false,
11784                },
11785                project.clone(),
11786                window,
11787                cx,
11788            )
11789        })
11790        .unwrap();
11791
11792    let fake_server = fake_servers.next().await.unwrap();
11793    fake_server
11794        .server
11795        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
11796            Ok(Some(vec![lsp::TextEdit::new(
11797                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11798                format!("[{} formatted]", params.text_document.uri),
11799            )]))
11800        })
11801        .detach();
11802    save.await;
11803
11804    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
11805    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
11806    assert_eq!(
11807        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11808        uri!(
11809            "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}"
11810        ),
11811    );
11812    buffer_1.update(cx, |buffer, _| {
11813        assert!(!buffer.is_dirty());
11814        assert_eq!(
11815            buffer.text(),
11816            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
11817        )
11818    });
11819    buffer_2.update(cx, |buffer, _| {
11820        assert!(!buffer.is_dirty());
11821        assert_eq!(
11822            buffer.text(),
11823            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
11824        )
11825    });
11826    buffer_3.update(cx, |buffer, _| {
11827        assert!(!buffer.is_dirty());
11828        assert_eq!(buffer.text(), sample_text_3,)
11829    });
11830}
11831
11832#[gpui::test]
11833async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
11834    init_test(cx, |_| {});
11835
11836    let fs = FakeFs::new(cx.executor());
11837    fs.insert_tree(
11838        path!("/dir"),
11839        json!({
11840            "file1.rs": "fn main() { println!(\"hello\"); }",
11841            "file2.rs": "fn test() { println!(\"test\"); }",
11842            "file3.rs": "fn other() { println!(\"other\"); }\n",
11843        }),
11844    )
11845    .await;
11846
11847    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
11848    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11849    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11850
11851    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11852    language_registry.add(rust_lang());
11853
11854    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
11855    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11856
11857    // Open three buffers
11858    let buffer_1 = project
11859        .update(cx, |project, cx| {
11860            project.open_buffer((worktree_id, rel_path("file1.rs")), cx)
11861        })
11862        .await
11863        .unwrap();
11864    let buffer_2 = project
11865        .update(cx, |project, cx| {
11866            project.open_buffer((worktree_id, rel_path("file2.rs")), cx)
11867        })
11868        .await
11869        .unwrap();
11870    let buffer_3 = project
11871        .update(cx, |project, cx| {
11872            project.open_buffer((worktree_id, rel_path("file3.rs")), cx)
11873        })
11874        .await
11875        .unwrap();
11876
11877    // Create a multi-buffer with all three buffers
11878    let multi_buffer = cx.new(|cx| {
11879        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11880        multi_buffer.push_excerpts(
11881            buffer_1.clone(),
11882            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11883            cx,
11884        );
11885        multi_buffer.push_excerpts(
11886            buffer_2.clone(),
11887            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11888            cx,
11889        );
11890        multi_buffer.push_excerpts(
11891            buffer_3.clone(),
11892            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11893            cx,
11894        );
11895        multi_buffer
11896    });
11897
11898    let editor = cx.new_window_entity(|window, cx| {
11899        Editor::new(
11900            EditorMode::full(),
11901            multi_buffer,
11902            Some(project.clone()),
11903            window,
11904            cx,
11905        )
11906    });
11907
11908    // Edit only the first buffer
11909    editor.update_in(cx, |editor, window, cx| {
11910        editor.change_selections(
11911            SelectionEffects::scroll(Autoscroll::Next),
11912            window,
11913            cx,
11914            |s| s.select_ranges(Some(10..10)),
11915        );
11916        editor.insert("// edited", window, cx);
11917    });
11918
11919    // Verify that only buffer 1 is dirty
11920    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
11921    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11922    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11923
11924    // Get write counts after file creation (files were created with initial content)
11925    // We expect each file to have been written once during creation
11926    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
11927    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
11928    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
11929
11930    // Perform autosave
11931    let save_task = editor.update_in(cx, |editor, window, cx| {
11932        editor.save(
11933            SaveOptions {
11934                format: true,
11935                autosave: true,
11936            },
11937            project.clone(),
11938            window,
11939            cx,
11940        )
11941    });
11942    save_task.await.unwrap();
11943
11944    // Only the dirty buffer should have been saved
11945    assert_eq!(
11946        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
11947        1,
11948        "Buffer 1 was dirty, so it should have been written once during autosave"
11949    );
11950    assert_eq!(
11951        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
11952        0,
11953        "Buffer 2 was clean, so it should not have been written during autosave"
11954    );
11955    assert_eq!(
11956        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
11957        0,
11958        "Buffer 3 was clean, so it should not have been written during autosave"
11959    );
11960
11961    // Verify buffer states after autosave
11962    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11963    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11964    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11965
11966    // Now perform a manual save (format = true)
11967    let save_task = editor.update_in(cx, |editor, window, cx| {
11968        editor.save(
11969            SaveOptions {
11970                format: true,
11971                autosave: false,
11972            },
11973            project.clone(),
11974            window,
11975            cx,
11976        )
11977    });
11978    save_task.await.unwrap();
11979
11980    // During manual save, clean buffers don't get written to disk
11981    // They just get did_save called for language server notifications
11982    assert_eq!(
11983        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
11984        1,
11985        "Buffer 1 should only have been written once total (during autosave, not manual save)"
11986    );
11987    assert_eq!(
11988        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
11989        0,
11990        "Buffer 2 should not have been written at all"
11991    );
11992    assert_eq!(
11993        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
11994        0,
11995        "Buffer 3 should not have been written at all"
11996    );
11997}
11998
11999async fn setup_range_format_test(
12000    cx: &mut TestAppContext,
12001) -> (
12002    Entity<Project>,
12003    Entity<Editor>,
12004    &mut gpui::VisualTestContext,
12005    lsp::FakeLanguageServer,
12006) {
12007    init_test(cx, |_| {});
12008
12009    let fs = FakeFs::new(cx.executor());
12010    fs.insert_file(path!("/file.rs"), Default::default()).await;
12011
12012    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12013
12014    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12015    language_registry.add(rust_lang());
12016    let mut fake_servers = language_registry.register_fake_lsp(
12017        "Rust",
12018        FakeLspAdapter {
12019            capabilities: lsp::ServerCapabilities {
12020                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
12021                ..lsp::ServerCapabilities::default()
12022            },
12023            ..FakeLspAdapter::default()
12024        },
12025    );
12026
12027    let buffer = project
12028        .update(cx, |project, cx| {
12029            project.open_local_buffer(path!("/file.rs"), cx)
12030        })
12031        .await
12032        .unwrap();
12033
12034    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12035    let (editor, cx) = cx.add_window_view(|window, cx| {
12036        build_editor_with_project(project.clone(), buffer, window, cx)
12037    });
12038
12039    cx.executor().start_waiting();
12040    let fake_server = fake_servers.next().await.unwrap();
12041
12042    (project, editor, cx, fake_server)
12043}
12044
12045#[gpui::test]
12046async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
12047    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12048
12049    editor.update_in(cx, |editor, window, cx| {
12050        editor.set_text("one\ntwo\nthree\n", window, cx)
12051    });
12052    assert!(cx.read(|cx| editor.is_dirty(cx)));
12053
12054    let save = editor
12055        .update_in(cx, |editor, window, cx| {
12056            editor.save(
12057                SaveOptions {
12058                    format: true,
12059                    autosave: false,
12060                },
12061                project.clone(),
12062                window,
12063                cx,
12064            )
12065        })
12066        .unwrap();
12067    fake_server
12068        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
12069            assert_eq!(
12070                params.text_document.uri,
12071                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12072            );
12073            assert_eq!(params.options.tab_size, 4);
12074            Ok(Some(vec![lsp::TextEdit::new(
12075                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
12076                ", ".to_string(),
12077            )]))
12078        })
12079        .next()
12080        .await;
12081    cx.executor().start_waiting();
12082    save.await;
12083    assert_eq!(
12084        editor.update(cx, |editor, cx| editor.text(cx)),
12085        "one, two\nthree\n"
12086    );
12087    assert!(!cx.read(|cx| editor.is_dirty(cx)));
12088}
12089
12090#[gpui::test]
12091async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
12092    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12093
12094    editor.update_in(cx, |editor, window, cx| {
12095        editor.set_text("one\ntwo\nthree\n", window, cx)
12096    });
12097    assert!(cx.read(|cx| editor.is_dirty(cx)));
12098
12099    // Test that save still works when formatting hangs
12100    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
12101        move |params, _| async move {
12102            assert_eq!(
12103                params.text_document.uri,
12104                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12105            );
12106            futures::future::pending::<()>().await;
12107            unreachable!()
12108        },
12109    );
12110    let save = editor
12111        .update_in(cx, |editor, window, cx| {
12112            editor.save(
12113                SaveOptions {
12114                    format: true,
12115                    autosave: false,
12116                },
12117                project.clone(),
12118                window,
12119                cx,
12120            )
12121        })
12122        .unwrap();
12123    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
12124    cx.executor().start_waiting();
12125    save.await;
12126    assert_eq!(
12127        editor.update(cx, |editor, cx| editor.text(cx)),
12128        "one\ntwo\nthree\n"
12129    );
12130    assert!(!cx.read(|cx| editor.is_dirty(cx)));
12131}
12132
12133#[gpui::test]
12134async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
12135    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12136
12137    // Buffer starts clean, no formatting should be requested
12138    let save = editor
12139        .update_in(cx, |editor, window, cx| {
12140            editor.save(
12141                SaveOptions {
12142                    format: false,
12143                    autosave: false,
12144                },
12145                project.clone(),
12146                window,
12147                cx,
12148            )
12149        })
12150        .unwrap();
12151    let _pending_format_request = fake_server
12152        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
12153            panic!("Should not be invoked");
12154        })
12155        .next();
12156    cx.executor().start_waiting();
12157    save.await;
12158    cx.run_until_parked();
12159}
12160
12161#[gpui::test]
12162async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
12163    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12164
12165    // Set Rust language override and assert overridden tabsize is sent to language server
12166    update_test_language_settings(cx, |settings| {
12167        settings.languages.0.insert(
12168            "Rust".into(),
12169            LanguageSettingsContent {
12170                tab_size: NonZeroU32::new(8),
12171                ..Default::default()
12172            },
12173        );
12174    });
12175
12176    editor.update_in(cx, |editor, window, cx| {
12177        editor.set_text("something_new\n", window, cx)
12178    });
12179    assert!(cx.read(|cx| editor.is_dirty(cx)));
12180    let save = editor
12181        .update_in(cx, |editor, window, cx| {
12182            editor.save(
12183                SaveOptions {
12184                    format: true,
12185                    autosave: false,
12186                },
12187                project.clone(),
12188                window,
12189                cx,
12190            )
12191        })
12192        .unwrap();
12193    fake_server
12194        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
12195            assert_eq!(
12196                params.text_document.uri,
12197                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12198            );
12199            assert_eq!(params.options.tab_size, 8);
12200            Ok(Some(Vec::new()))
12201        })
12202        .next()
12203        .await;
12204    save.await;
12205}
12206
12207#[gpui::test]
12208async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
12209    init_test(cx, |settings| {
12210        settings.defaults.formatter = Some(FormatterList::Single(Formatter::LanguageServer(
12211            settings::LanguageServerFormatterSpecifier::Current,
12212        )))
12213    });
12214
12215    let fs = FakeFs::new(cx.executor());
12216    fs.insert_file(path!("/file.rs"), Default::default()).await;
12217
12218    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12219
12220    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12221    language_registry.add(Arc::new(Language::new(
12222        LanguageConfig {
12223            name: "Rust".into(),
12224            matcher: LanguageMatcher {
12225                path_suffixes: vec!["rs".to_string()],
12226                ..Default::default()
12227            },
12228            ..LanguageConfig::default()
12229        },
12230        Some(tree_sitter_rust::LANGUAGE.into()),
12231    )));
12232    update_test_language_settings(cx, |settings| {
12233        // Enable Prettier formatting for the same buffer, and ensure
12234        // LSP is called instead of Prettier.
12235        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
12236    });
12237    let mut fake_servers = language_registry.register_fake_lsp(
12238        "Rust",
12239        FakeLspAdapter {
12240            capabilities: lsp::ServerCapabilities {
12241                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12242                ..Default::default()
12243            },
12244            ..Default::default()
12245        },
12246    );
12247
12248    let buffer = project
12249        .update(cx, |project, cx| {
12250            project.open_local_buffer(path!("/file.rs"), cx)
12251        })
12252        .await
12253        .unwrap();
12254
12255    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12256    let (editor, cx) = cx.add_window_view(|window, cx| {
12257        build_editor_with_project(project.clone(), buffer, window, cx)
12258    });
12259    editor.update_in(cx, |editor, window, cx| {
12260        editor.set_text("one\ntwo\nthree\n", window, cx)
12261    });
12262
12263    cx.executor().start_waiting();
12264    let fake_server = fake_servers.next().await.unwrap();
12265
12266    let format = editor
12267        .update_in(cx, |editor, window, cx| {
12268            editor.perform_format(
12269                project.clone(),
12270                FormatTrigger::Manual,
12271                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12272                window,
12273                cx,
12274            )
12275        })
12276        .unwrap();
12277    fake_server
12278        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
12279            assert_eq!(
12280                params.text_document.uri,
12281                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12282            );
12283            assert_eq!(params.options.tab_size, 4);
12284            Ok(Some(vec![lsp::TextEdit::new(
12285                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
12286                ", ".to_string(),
12287            )]))
12288        })
12289        .next()
12290        .await;
12291    cx.executor().start_waiting();
12292    format.await;
12293    assert_eq!(
12294        editor.update(cx, |editor, cx| editor.text(cx)),
12295        "one, two\nthree\n"
12296    );
12297
12298    editor.update_in(cx, |editor, window, cx| {
12299        editor.set_text("one\ntwo\nthree\n", window, cx)
12300    });
12301    // Ensure we don't lock if formatting hangs.
12302    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12303        move |params, _| async move {
12304            assert_eq!(
12305                params.text_document.uri,
12306                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12307            );
12308            futures::future::pending::<()>().await;
12309            unreachable!()
12310        },
12311    );
12312    let format = editor
12313        .update_in(cx, |editor, window, cx| {
12314            editor.perform_format(
12315                project,
12316                FormatTrigger::Manual,
12317                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12318                window,
12319                cx,
12320            )
12321        })
12322        .unwrap();
12323    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
12324    cx.executor().start_waiting();
12325    format.await;
12326    assert_eq!(
12327        editor.update(cx, |editor, cx| editor.text(cx)),
12328        "one\ntwo\nthree\n"
12329    );
12330}
12331
12332#[gpui::test]
12333async fn test_multiple_formatters(cx: &mut TestAppContext) {
12334    init_test(cx, |settings| {
12335        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
12336        settings.defaults.formatter = Some(FormatterList::Vec(vec![
12337            Formatter::LanguageServer(settings::LanguageServerFormatterSpecifier::Current),
12338            Formatter::CodeAction("code-action-1".into()),
12339            Formatter::CodeAction("code-action-2".into()),
12340        ]))
12341    });
12342
12343    let fs = FakeFs::new(cx.executor());
12344    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
12345        .await;
12346
12347    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12348    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12349    language_registry.add(rust_lang());
12350
12351    let mut fake_servers = language_registry.register_fake_lsp(
12352        "Rust",
12353        FakeLspAdapter {
12354            capabilities: lsp::ServerCapabilities {
12355                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12356                execute_command_provider: Some(lsp::ExecuteCommandOptions {
12357                    commands: vec!["the-command-for-code-action-1".into()],
12358                    ..Default::default()
12359                }),
12360                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
12361                ..Default::default()
12362            },
12363            ..Default::default()
12364        },
12365    );
12366
12367    let buffer = project
12368        .update(cx, |project, cx| {
12369            project.open_local_buffer(path!("/file.rs"), cx)
12370        })
12371        .await
12372        .unwrap();
12373
12374    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12375    let (editor, cx) = cx.add_window_view(|window, cx| {
12376        build_editor_with_project(project.clone(), buffer, window, cx)
12377    });
12378
12379    cx.executor().start_waiting();
12380
12381    let fake_server = fake_servers.next().await.unwrap();
12382    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12383        move |_params, _| async move {
12384            Ok(Some(vec![lsp::TextEdit::new(
12385                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12386                "applied-formatting\n".to_string(),
12387            )]))
12388        },
12389    );
12390    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
12391        move |params, _| async move {
12392            let requested_code_actions = params.context.only.expect("Expected code action request");
12393            assert_eq!(requested_code_actions.len(), 1);
12394
12395            let uri = lsp::Uri::from_file_path(path!("/file.rs")).unwrap();
12396            let code_action = match requested_code_actions[0].as_str() {
12397                "code-action-1" => lsp::CodeAction {
12398                    kind: Some("code-action-1".into()),
12399                    edit: Some(lsp::WorkspaceEdit::new(
12400                        [(
12401                            uri,
12402                            vec![lsp::TextEdit::new(
12403                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12404                                "applied-code-action-1-edit\n".to_string(),
12405                            )],
12406                        )]
12407                        .into_iter()
12408                        .collect(),
12409                    )),
12410                    command: Some(lsp::Command {
12411                        command: "the-command-for-code-action-1".into(),
12412                        ..Default::default()
12413                    }),
12414                    ..Default::default()
12415                },
12416                "code-action-2" => lsp::CodeAction {
12417                    kind: Some("code-action-2".into()),
12418                    edit: Some(lsp::WorkspaceEdit::new(
12419                        [(
12420                            uri,
12421                            vec![lsp::TextEdit::new(
12422                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12423                                "applied-code-action-2-edit\n".to_string(),
12424                            )],
12425                        )]
12426                        .into_iter()
12427                        .collect(),
12428                    )),
12429                    ..Default::default()
12430                },
12431                req => panic!("Unexpected code action request: {:?}", req),
12432            };
12433            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
12434                code_action,
12435            )]))
12436        },
12437    );
12438
12439    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
12440        move |params, _| async move { Ok(params) }
12441    });
12442
12443    let command_lock = Arc::new(futures::lock::Mutex::new(()));
12444    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
12445        let fake = fake_server.clone();
12446        let lock = command_lock.clone();
12447        move |params, _| {
12448            assert_eq!(params.command, "the-command-for-code-action-1");
12449            let fake = fake.clone();
12450            let lock = lock.clone();
12451            async move {
12452                lock.lock().await;
12453                fake.server
12454                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
12455                        label: None,
12456                        edit: lsp::WorkspaceEdit {
12457                            changes: Some(
12458                                [(
12459                                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
12460                                    vec![lsp::TextEdit {
12461                                        range: lsp::Range::new(
12462                                            lsp::Position::new(0, 0),
12463                                            lsp::Position::new(0, 0),
12464                                        ),
12465                                        new_text: "applied-code-action-1-command\n".into(),
12466                                    }],
12467                                )]
12468                                .into_iter()
12469                                .collect(),
12470                            ),
12471                            ..Default::default()
12472                        },
12473                    })
12474                    .await
12475                    .into_response()
12476                    .unwrap();
12477                Ok(Some(json!(null)))
12478            }
12479        }
12480    });
12481
12482    cx.executor().start_waiting();
12483    editor
12484        .update_in(cx, |editor, window, cx| {
12485            editor.perform_format(
12486                project.clone(),
12487                FormatTrigger::Manual,
12488                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12489                window,
12490                cx,
12491            )
12492        })
12493        .unwrap()
12494        .await;
12495    editor.update(cx, |editor, cx| {
12496        assert_eq!(
12497            editor.text(cx),
12498            r#"
12499                applied-code-action-2-edit
12500                applied-code-action-1-command
12501                applied-code-action-1-edit
12502                applied-formatting
12503                one
12504                two
12505                three
12506            "#
12507            .unindent()
12508        );
12509    });
12510
12511    editor.update_in(cx, |editor, window, cx| {
12512        editor.undo(&Default::default(), window, cx);
12513        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12514    });
12515
12516    // Perform a manual edit while waiting for an LSP command
12517    // that's being run as part of a formatting code action.
12518    let lock_guard = command_lock.lock().await;
12519    let format = editor
12520        .update_in(cx, |editor, window, cx| {
12521            editor.perform_format(
12522                project.clone(),
12523                FormatTrigger::Manual,
12524                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12525                window,
12526                cx,
12527            )
12528        })
12529        .unwrap();
12530    cx.run_until_parked();
12531    editor.update(cx, |editor, cx| {
12532        assert_eq!(
12533            editor.text(cx),
12534            r#"
12535                applied-code-action-1-edit
12536                applied-formatting
12537                one
12538                two
12539                three
12540            "#
12541            .unindent()
12542        );
12543
12544        editor.buffer.update(cx, |buffer, cx| {
12545            let ix = buffer.len(cx);
12546            buffer.edit([(ix..ix, "edited\n")], None, cx);
12547        });
12548    });
12549
12550    // Allow the LSP command to proceed. Because the buffer was edited,
12551    // the second code action will not be run.
12552    drop(lock_guard);
12553    format.await;
12554    editor.update_in(cx, |editor, window, cx| {
12555        assert_eq!(
12556            editor.text(cx),
12557            r#"
12558                applied-code-action-1-command
12559                applied-code-action-1-edit
12560                applied-formatting
12561                one
12562                two
12563                three
12564                edited
12565            "#
12566            .unindent()
12567        );
12568
12569        // The manual edit is undone first, because it is the last thing the user did
12570        // (even though the command completed afterwards).
12571        editor.undo(&Default::default(), window, cx);
12572        assert_eq!(
12573            editor.text(cx),
12574            r#"
12575                applied-code-action-1-command
12576                applied-code-action-1-edit
12577                applied-formatting
12578                one
12579                two
12580                three
12581            "#
12582            .unindent()
12583        );
12584
12585        // All the formatting (including the command, which completed after the manual edit)
12586        // is undone together.
12587        editor.undo(&Default::default(), window, cx);
12588        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12589    });
12590}
12591
12592#[gpui::test]
12593async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
12594    init_test(cx, |settings| {
12595        settings.defaults.formatter = Some(FormatterList::Vec(vec![Formatter::LanguageServer(
12596            settings::LanguageServerFormatterSpecifier::Current,
12597        )]))
12598    });
12599
12600    let fs = FakeFs::new(cx.executor());
12601    fs.insert_file(path!("/file.ts"), Default::default()).await;
12602
12603    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12604
12605    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12606    language_registry.add(Arc::new(Language::new(
12607        LanguageConfig {
12608            name: "TypeScript".into(),
12609            matcher: LanguageMatcher {
12610                path_suffixes: vec!["ts".to_string()],
12611                ..Default::default()
12612            },
12613            ..LanguageConfig::default()
12614        },
12615        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12616    )));
12617    update_test_language_settings(cx, |settings| {
12618        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
12619    });
12620    let mut fake_servers = language_registry.register_fake_lsp(
12621        "TypeScript",
12622        FakeLspAdapter {
12623            capabilities: lsp::ServerCapabilities {
12624                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
12625                ..Default::default()
12626            },
12627            ..Default::default()
12628        },
12629    );
12630
12631    let buffer = project
12632        .update(cx, |project, cx| {
12633            project.open_local_buffer(path!("/file.ts"), cx)
12634        })
12635        .await
12636        .unwrap();
12637
12638    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12639    let (editor, cx) = cx.add_window_view(|window, cx| {
12640        build_editor_with_project(project.clone(), buffer, window, cx)
12641    });
12642    editor.update_in(cx, |editor, window, cx| {
12643        editor.set_text(
12644            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12645            window,
12646            cx,
12647        )
12648    });
12649
12650    cx.executor().start_waiting();
12651    let fake_server = fake_servers.next().await.unwrap();
12652
12653    let format = editor
12654        .update_in(cx, |editor, window, cx| {
12655            editor.perform_code_action_kind(
12656                project.clone(),
12657                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12658                window,
12659                cx,
12660            )
12661        })
12662        .unwrap();
12663    fake_server
12664        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
12665            assert_eq!(
12666                params.text_document.uri,
12667                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12668            );
12669            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
12670                lsp::CodeAction {
12671                    title: "Organize Imports".to_string(),
12672                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
12673                    edit: Some(lsp::WorkspaceEdit {
12674                        changes: Some(
12675                            [(
12676                                params.text_document.uri.clone(),
12677                                vec![lsp::TextEdit::new(
12678                                    lsp::Range::new(
12679                                        lsp::Position::new(1, 0),
12680                                        lsp::Position::new(2, 0),
12681                                    ),
12682                                    "".to_string(),
12683                                )],
12684                            )]
12685                            .into_iter()
12686                            .collect(),
12687                        ),
12688                        ..Default::default()
12689                    }),
12690                    ..Default::default()
12691                },
12692            )]))
12693        })
12694        .next()
12695        .await;
12696    cx.executor().start_waiting();
12697    format.await;
12698    assert_eq!(
12699        editor.update(cx, |editor, cx| editor.text(cx)),
12700        "import { a } from 'module';\n\nconst x = a;\n"
12701    );
12702
12703    editor.update_in(cx, |editor, window, cx| {
12704        editor.set_text(
12705            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12706            window,
12707            cx,
12708        )
12709    });
12710    // Ensure we don't lock if code action hangs.
12711    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
12712        move |params, _| async move {
12713            assert_eq!(
12714                params.text_document.uri,
12715                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12716            );
12717            futures::future::pending::<()>().await;
12718            unreachable!()
12719        },
12720    );
12721    let format = editor
12722        .update_in(cx, |editor, window, cx| {
12723            editor.perform_code_action_kind(
12724                project,
12725                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12726                window,
12727                cx,
12728            )
12729        })
12730        .unwrap();
12731    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
12732    cx.executor().start_waiting();
12733    format.await;
12734    assert_eq!(
12735        editor.update(cx, |editor, cx| editor.text(cx)),
12736        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
12737    );
12738}
12739
12740#[gpui::test]
12741async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
12742    init_test(cx, |_| {});
12743
12744    let mut cx = EditorLspTestContext::new_rust(
12745        lsp::ServerCapabilities {
12746            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12747            ..Default::default()
12748        },
12749        cx,
12750    )
12751    .await;
12752
12753    cx.set_state(indoc! {"
12754        one.twoˇ
12755    "});
12756
12757    // The format request takes a long time. When it completes, it inserts
12758    // a newline and an indent before the `.`
12759    cx.lsp
12760        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
12761            let executor = cx.background_executor().clone();
12762            async move {
12763                executor.timer(Duration::from_millis(100)).await;
12764                Ok(Some(vec![lsp::TextEdit {
12765                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
12766                    new_text: "\n    ".into(),
12767                }]))
12768            }
12769        });
12770
12771    // Submit a format request.
12772    let format_1 = cx
12773        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12774        .unwrap();
12775    cx.executor().run_until_parked();
12776
12777    // Submit a second format request.
12778    let format_2 = cx
12779        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12780        .unwrap();
12781    cx.executor().run_until_parked();
12782
12783    // Wait for both format requests to complete
12784    cx.executor().advance_clock(Duration::from_millis(200));
12785    cx.executor().start_waiting();
12786    format_1.await.unwrap();
12787    cx.executor().start_waiting();
12788    format_2.await.unwrap();
12789
12790    // The formatting edits only happens once.
12791    cx.assert_editor_state(indoc! {"
12792        one
12793            .twoˇ
12794    "});
12795}
12796
12797#[gpui::test]
12798async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
12799    init_test(cx, |settings| {
12800        settings.defaults.formatter = Some(FormatterList::default())
12801    });
12802
12803    let mut cx = EditorLspTestContext::new_rust(
12804        lsp::ServerCapabilities {
12805            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12806            ..Default::default()
12807        },
12808        cx,
12809    )
12810    .await;
12811
12812    // Record which buffer changes have been sent to the language server
12813    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
12814    cx.lsp
12815        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
12816            let buffer_changes = buffer_changes.clone();
12817            move |params, _| {
12818                buffer_changes.lock().extend(
12819                    params
12820                        .content_changes
12821                        .into_iter()
12822                        .map(|e| (e.range.unwrap(), e.text)),
12823                );
12824            }
12825        });
12826    // Handle formatting requests to the language server.
12827    cx.lsp
12828        .set_request_handler::<lsp::request::Formatting, _, _>({
12829            let buffer_changes = buffer_changes.clone();
12830            move |_, _| {
12831                let buffer_changes = buffer_changes.clone();
12832                // Insert blank lines between each line of the buffer.
12833                async move {
12834                    // When formatting is requested, trailing whitespace has already been stripped,
12835                    // and the trailing newline has already been added.
12836                    assert_eq!(
12837                        &buffer_changes.lock()[1..],
12838                        &[
12839                            (
12840                                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
12841                                "".into()
12842                            ),
12843                            (
12844                                lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
12845                                "".into()
12846                            ),
12847                            (
12848                                lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
12849                                "\n".into()
12850                            ),
12851                        ]
12852                    );
12853
12854                    Ok(Some(vec![
12855                        lsp::TextEdit {
12856                            range: lsp::Range::new(
12857                                lsp::Position::new(1, 0),
12858                                lsp::Position::new(1, 0),
12859                            ),
12860                            new_text: "\n".into(),
12861                        },
12862                        lsp::TextEdit {
12863                            range: lsp::Range::new(
12864                                lsp::Position::new(2, 0),
12865                                lsp::Position::new(2, 0),
12866                            ),
12867                            new_text: "\n".into(),
12868                        },
12869                    ]))
12870                }
12871            }
12872        });
12873
12874    // Set up a buffer white some trailing whitespace and no trailing newline.
12875    cx.set_state(
12876        &[
12877            "one ",   //
12878            "twoˇ",   //
12879            "three ", //
12880            "four",   //
12881        ]
12882        .join("\n"),
12883    );
12884    cx.run_until_parked();
12885
12886    // Submit a format request.
12887    let format = cx
12888        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12889        .unwrap();
12890
12891    cx.run_until_parked();
12892    // After formatting the buffer, the trailing whitespace is stripped,
12893    // a newline is appended, and the edits provided by the language server
12894    // have been applied.
12895    format.await.unwrap();
12896
12897    cx.assert_editor_state(
12898        &[
12899            "one",   //
12900            "",      //
12901            "twoˇ",  //
12902            "",      //
12903            "three", //
12904            "four",  //
12905            "",      //
12906        ]
12907        .join("\n"),
12908    );
12909
12910    // Undoing the formatting undoes the trailing whitespace removal, the
12911    // trailing newline, and the LSP edits.
12912    cx.update_buffer(|buffer, cx| buffer.undo(cx));
12913    cx.assert_editor_state(
12914        &[
12915            "one ",   //
12916            "twoˇ",   //
12917            "three ", //
12918            "four",   //
12919        ]
12920        .join("\n"),
12921    );
12922}
12923
12924#[gpui::test]
12925async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
12926    cx: &mut TestAppContext,
12927) {
12928    init_test(cx, |_| {});
12929
12930    cx.update(|cx| {
12931        cx.update_global::<SettingsStore, _>(|settings, cx| {
12932            settings.update_user_settings(cx, |settings| {
12933                settings.editor.auto_signature_help = Some(true);
12934            });
12935        });
12936    });
12937
12938    let mut cx = EditorLspTestContext::new_rust(
12939        lsp::ServerCapabilities {
12940            signature_help_provider: Some(lsp::SignatureHelpOptions {
12941                ..Default::default()
12942            }),
12943            ..Default::default()
12944        },
12945        cx,
12946    )
12947    .await;
12948
12949    let language = Language::new(
12950        LanguageConfig {
12951            name: "Rust".into(),
12952            brackets: BracketPairConfig {
12953                pairs: vec![
12954                    BracketPair {
12955                        start: "{".to_string(),
12956                        end: "}".to_string(),
12957                        close: true,
12958                        surround: true,
12959                        newline: true,
12960                    },
12961                    BracketPair {
12962                        start: "(".to_string(),
12963                        end: ")".to_string(),
12964                        close: true,
12965                        surround: true,
12966                        newline: true,
12967                    },
12968                    BracketPair {
12969                        start: "/*".to_string(),
12970                        end: " */".to_string(),
12971                        close: true,
12972                        surround: true,
12973                        newline: true,
12974                    },
12975                    BracketPair {
12976                        start: "[".to_string(),
12977                        end: "]".to_string(),
12978                        close: false,
12979                        surround: false,
12980                        newline: true,
12981                    },
12982                    BracketPair {
12983                        start: "\"".to_string(),
12984                        end: "\"".to_string(),
12985                        close: true,
12986                        surround: true,
12987                        newline: false,
12988                    },
12989                    BracketPair {
12990                        start: "<".to_string(),
12991                        end: ">".to_string(),
12992                        close: false,
12993                        surround: true,
12994                        newline: true,
12995                    },
12996                ],
12997                ..Default::default()
12998            },
12999            autoclose_before: "})]".to_string(),
13000            ..Default::default()
13001        },
13002        Some(tree_sitter_rust::LANGUAGE.into()),
13003    );
13004    let language = Arc::new(language);
13005
13006    cx.language_registry().add(language.clone());
13007    cx.update_buffer(|buffer, cx| {
13008        buffer.set_language(Some(language), cx);
13009    });
13010
13011    cx.set_state(
13012        &r#"
13013            fn main() {
13014                sampleˇ
13015            }
13016        "#
13017        .unindent(),
13018    );
13019
13020    cx.update_editor(|editor, window, cx| {
13021        editor.handle_input("(", window, cx);
13022    });
13023    cx.assert_editor_state(
13024        &"
13025            fn main() {
13026                sample(ˇ)
13027            }
13028        "
13029        .unindent(),
13030    );
13031
13032    let mocked_response = lsp::SignatureHelp {
13033        signatures: vec![lsp::SignatureInformation {
13034            label: "fn sample(param1: u8, param2: u8)".to_string(),
13035            documentation: None,
13036            parameters: Some(vec![
13037                lsp::ParameterInformation {
13038                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13039                    documentation: None,
13040                },
13041                lsp::ParameterInformation {
13042                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13043                    documentation: None,
13044                },
13045            ]),
13046            active_parameter: None,
13047        }],
13048        active_signature: Some(0),
13049        active_parameter: Some(0),
13050    };
13051    handle_signature_help_request(&mut cx, mocked_response).await;
13052
13053    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13054        .await;
13055
13056    cx.editor(|editor, _, _| {
13057        let signature_help_state = editor.signature_help_state.popover().cloned();
13058        let signature = signature_help_state.unwrap();
13059        assert_eq!(
13060            signature.signatures[signature.current_signature].label,
13061            "fn sample(param1: u8, param2: u8)"
13062        );
13063    });
13064}
13065
13066#[gpui::test]
13067async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
13068    init_test(cx, |_| {});
13069
13070    cx.update(|cx| {
13071        cx.update_global::<SettingsStore, _>(|settings, cx| {
13072            settings.update_user_settings(cx, |settings| {
13073                settings.editor.auto_signature_help = Some(false);
13074                settings.editor.show_signature_help_after_edits = Some(false);
13075            });
13076        });
13077    });
13078
13079    let mut cx = EditorLspTestContext::new_rust(
13080        lsp::ServerCapabilities {
13081            signature_help_provider: Some(lsp::SignatureHelpOptions {
13082                ..Default::default()
13083            }),
13084            ..Default::default()
13085        },
13086        cx,
13087    )
13088    .await;
13089
13090    let language = Language::new(
13091        LanguageConfig {
13092            name: "Rust".into(),
13093            brackets: BracketPairConfig {
13094                pairs: vec![
13095                    BracketPair {
13096                        start: "{".to_string(),
13097                        end: "}".to_string(),
13098                        close: true,
13099                        surround: true,
13100                        newline: true,
13101                    },
13102                    BracketPair {
13103                        start: "(".to_string(),
13104                        end: ")".to_string(),
13105                        close: true,
13106                        surround: true,
13107                        newline: true,
13108                    },
13109                    BracketPair {
13110                        start: "/*".to_string(),
13111                        end: " */".to_string(),
13112                        close: true,
13113                        surround: true,
13114                        newline: true,
13115                    },
13116                    BracketPair {
13117                        start: "[".to_string(),
13118                        end: "]".to_string(),
13119                        close: false,
13120                        surround: false,
13121                        newline: true,
13122                    },
13123                    BracketPair {
13124                        start: "\"".to_string(),
13125                        end: "\"".to_string(),
13126                        close: true,
13127                        surround: true,
13128                        newline: false,
13129                    },
13130                    BracketPair {
13131                        start: "<".to_string(),
13132                        end: ">".to_string(),
13133                        close: false,
13134                        surround: true,
13135                        newline: true,
13136                    },
13137                ],
13138                ..Default::default()
13139            },
13140            autoclose_before: "})]".to_string(),
13141            ..Default::default()
13142        },
13143        Some(tree_sitter_rust::LANGUAGE.into()),
13144    );
13145    let language = Arc::new(language);
13146
13147    cx.language_registry().add(language.clone());
13148    cx.update_buffer(|buffer, cx| {
13149        buffer.set_language(Some(language), cx);
13150    });
13151
13152    // Ensure that signature_help is not called when no signature help is enabled.
13153    cx.set_state(
13154        &r#"
13155            fn main() {
13156                sampleˇ
13157            }
13158        "#
13159        .unindent(),
13160    );
13161    cx.update_editor(|editor, window, cx| {
13162        editor.handle_input("(", window, cx);
13163    });
13164    cx.assert_editor_state(
13165        &"
13166            fn main() {
13167                sample(ˇ)
13168            }
13169        "
13170        .unindent(),
13171    );
13172    cx.editor(|editor, _, _| {
13173        assert!(editor.signature_help_state.task().is_none());
13174    });
13175
13176    let mocked_response = lsp::SignatureHelp {
13177        signatures: vec![lsp::SignatureInformation {
13178            label: "fn sample(param1: u8, param2: u8)".to_string(),
13179            documentation: None,
13180            parameters: Some(vec![
13181                lsp::ParameterInformation {
13182                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13183                    documentation: None,
13184                },
13185                lsp::ParameterInformation {
13186                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13187                    documentation: None,
13188                },
13189            ]),
13190            active_parameter: None,
13191        }],
13192        active_signature: Some(0),
13193        active_parameter: Some(0),
13194    };
13195
13196    // Ensure that signature_help is called when enabled afte edits
13197    cx.update(|_, cx| {
13198        cx.update_global::<SettingsStore, _>(|settings, cx| {
13199            settings.update_user_settings(cx, |settings| {
13200                settings.editor.auto_signature_help = Some(false);
13201                settings.editor.show_signature_help_after_edits = Some(true);
13202            });
13203        });
13204    });
13205    cx.set_state(
13206        &r#"
13207            fn main() {
13208                sampleˇ
13209            }
13210        "#
13211        .unindent(),
13212    );
13213    cx.update_editor(|editor, window, cx| {
13214        editor.handle_input("(", window, cx);
13215    });
13216    cx.assert_editor_state(
13217        &"
13218            fn main() {
13219                sample(ˇ)
13220            }
13221        "
13222        .unindent(),
13223    );
13224    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13225    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13226        .await;
13227    cx.update_editor(|editor, _, _| {
13228        let signature_help_state = editor.signature_help_state.popover().cloned();
13229        assert!(signature_help_state.is_some());
13230        let signature = signature_help_state.unwrap();
13231        assert_eq!(
13232            signature.signatures[signature.current_signature].label,
13233            "fn sample(param1: u8, param2: u8)"
13234        );
13235        editor.signature_help_state = SignatureHelpState::default();
13236    });
13237
13238    // Ensure that signature_help is called when auto signature help override is enabled
13239    cx.update(|_, cx| {
13240        cx.update_global::<SettingsStore, _>(|settings, cx| {
13241            settings.update_user_settings(cx, |settings| {
13242                settings.editor.auto_signature_help = Some(true);
13243                settings.editor.show_signature_help_after_edits = Some(false);
13244            });
13245        });
13246    });
13247    cx.set_state(
13248        &r#"
13249            fn main() {
13250                sampleˇ
13251            }
13252        "#
13253        .unindent(),
13254    );
13255    cx.update_editor(|editor, window, cx| {
13256        editor.handle_input("(", window, cx);
13257    });
13258    cx.assert_editor_state(
13259        &"
13260            fn main() {
13261                sample(ˇ)
13262            }
13263        "
13264        .unindent(),
13265    );
13266    handle_signature_help_request(&mut cx, mocked_response).await;
13267    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13268        .await;
13269    cx.editor(|editor, _, _| {
13270        let signature_help_state = editor.signature_help_state.popover().cloned();
13271        assert!(signature_help_state.is_some());
13272        let signature = signature_help_state.unwrap();
13273        assert_eq!(
13274            signature.signatures[signature.current_signature].label,
13275            "fn sample(param1: u8, param2: u8)"
13276        );
13277    });
13278}
13279
13280#[gpui::test]
13281async fn test_signature_help(cx: &mut TestAppContext) {
13282    init_test(cx, |_| {});
13283    cx.update(|cx| {
13284        cx.update_global::<SettingsStore, _>(|settings, cx| {
13285            settings.update_user_settings(cx, |settings| {
13286                settings.editor.auto_signature_help = Some(true);
13287            });
13288        });
13289    });
13290
13291    let mut cx = EditorLspTestContext::new_rust(
13292        lsp::ServerCapabilities {
13293            signature_help_provider: Some(lsp::SignatureHelpOptions {
13294                ..Default::default()
13295            }),
13296            ..Default::default()
13297        },
13298        cx,
13299    )
13300    .await;
13301
13302    // A test that directly calls `show_signature_help`
13303    cx.update_editor(|editor, window, cx| {
13304        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13305    });
13306
13307    let mocked_response = lsp::SignatureHelp {
13308        signatures: vec![lsp::SignatureInformation {
13309            label: "fn sample(param1: u8, param2: u8)".to_string(),
13310            documentation: None,
13311            parameters: Some(vec![
13312                lsp::ParameterInformation {
13313                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13314                    documentation: None,
13315                },
13316                lsp::ParameterInformation {
13317                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13318                    documentation: None,
13319                },
13320            ]),
13321            active_parameter: None,
13322        }],
13323        active_signature: Some(0),
13324        active_parameter: Some(0),
13325    };
13326    handle_signature_help_request(&mut cx, mocked_response).await;
13327
13328    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13329        .await;
13330
13331    cx.editor(|editor, _, _| {
13332        let signature_help_state = editor.signature_help_state.popover().cloned();
13333        assert!(signature_help_state.is_some());
13334        let signature = signature_help_state.unwrap();
13335        assert_eq!(
13336            signature.signatures[signature.current_signature].label,
13337            "fn sample(param1: u8, param2: u8)"
13338        );
13339    });
13340
13341    // When exiting outside from inside the brackets, `signature_help` is closed.
13342    cx.set_state(indoc! {"
13343        fn main() {
13344            sample(ˇ);
13345        }
13346
13347        fn sample(param1: u8, param2: u8) {}
13348    "});
13349
13350    cx.update_editor(|editor, window, cx| {
13351        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13352            s.select_ranges([0..0])
13353        });
13354    });
13355
13356    let mocked_response = lsp::SignatureHelp {
13357        signatures: Vec::new(),
13358        active_signature: None,
13359        active_parameter: None,
13360    };
13361    handle_signature_help_request(&mut cx, mocked_response).await;
13362
13363    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13364        .await;
13365
13366    cx.editor(|editor, _, _| {
13367        assert!(!editor.signature_help_state.is_shown());
13368    });
13369
13370    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
13371    cx.set_state(indoc! {"
13372        fn main() {
13373            sample(ˇ);
13374        }
13375
13376        fn sample(param1: u8, param2: u8) {}
13377    "});
13378
13379    let mocked_response = lsp::SignatureHelp {
13380        signatures: vec![lsp::SignatureInformation {
13381            label: "fn sample(param1: u8, param2: u8)".to_string(),
13382            documentation: None,
13383            parameters: Some(vec![
13384                lsp::ParameterInformation {
13385                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13386                    documentation: None,
13387                },
13388                lsp::ParameterInformation {
13389                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13390                    documentation: None,
13391                },
13392            ]),
13393            active_parameter: None,
13394        }],
13395        active_signature: Some(0),
13396        active_parameter: Some(0),
13397    };
13398    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13399    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13400        .await;
13401    cx.editor(|editor, _, _| {
13402        assert!(editor.signature_help_state.is_shown());
13403    });
13404
13405    // Restore the popover with more parameter input
13406    cx.set_state(indoc! {"
13407        fn main() {
13408            sample(param1, param2ˇ);
13409        }
13410
13411        fn sample(param1: u8, param2: u8) {}
13412    "});
13413
13414    let mocked_response = lsp::SignatureHelp {
13415        signatures: vec![lsp::SignatureInformation {
13416            label: "fn sample(param1: u8, param2: u8)".to_string(),
13417            documentation: None,
13418            parameters: Some(vec![
13419                lsp::ParameterInformation {
13420                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13421                    documentation: None,
13422                },
13423                lsp::ParameterInformation {
13424                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13425                    documentation: None,
13426                },
13427            ]),
13428            active_parameter: None,
13429        }],
13430        active_signature: Some(0),
13431        active_parameter: Some(1),
13432    };
13433    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13434    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13435        .await;
13436
13437    // When selecting a range, the popover is gone.
13438    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
13439    cx.update_editor(|editor, window, cx| {
13440        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13441            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13442        })
13443    });
13444    cx.assert_editor_state(indoc! {"
13445        fn main() {
13446            sample(param1, «ˇparam2»);
13447        }
13448
13449        fn sample(param1: u8, param2: u8) {}
13450    "});
13451    cx.editor(|editor, _, _| {
13452        assert!(!editor.signature_help_state.is_shown());
13453    });
13454
13455    // When unselecting again, the popover is back if within the brackets.
13456    cx.update_editor(|editor, window, cx| {
13457        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13458            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13459        })
13460    });
13461    cx.assert_editor_state(indoc! {"
13462        fn main() {
13463            sample(param1, ˇparam2);
13464        }
13465
13466        fn sample(param1: u8, param2: u8) {}
13467    "});
13468    handle_signature_help_request(&mut cx, mocked_response).await;
13469    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13470        .await;
13471    cx.editor(|editor, _, _| {
13472        assert!(editor.signature_help_state.is_shown());
13473    });
13474
13475    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
13476    cx.update_editor(|editor, window, cx| {
13477        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13478            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
13479            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13480        })
13481    });
13482    cx.assert_editor_state(indoc! {"
13483        fn main() {
13484            sample(param1, ˇparam2);
13485        }
13486
13487        fn sample(param1: u8, param2: u8) {}
13488    "});
13489
13490    let mocked_response = lsp::SignatureHelp {
13491        signatures: vec![lsp::SignatureInformation {
13492            label: "fn sample(param1: u8, param2: u8)".to_string(),
13493            documentation: None,
13494            parameters: Some(vec![
13495                lsp::ParameterInformation {
13496                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13497                    documentation: None,
13498                },
13499                lsp::ParameterInformation {
13500                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13501                    documentation: None,
13502                },
13503            ]),
13504            active_parameter: None,
13505        }],
13506        active_signature: Some(0),
13507        active_parameter: Some(1),
13508    };
13509    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13510    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13511        .await;
13512    cx.update_editor(|editor, _, cx| {
13513        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
13514    });
13515    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13516        .await;
13517    cx.update_editor(|editor, window, cx| {
13518        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13519            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13520        })
13521    });
13522    cx.assert_editor_state(indoc! {"
13523        fn main() {
13524            sample(param1, «ˇparam2»);
13525        }
13526
13527        fn sample(param1: u8, param2: u8) {}
13528    "});
13529    cx.update_editor(|editor, window, cx| {
13530        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13531            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13532        })
13533    });
13534    cx.assert_editor_state(indoc! {"
13535        fn main() {
13536            sample(param1, ˇparam2);
13537        }
13538
13539        fn sample(param1: u8, param2: u8) {}
13540    "});
13541    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
13542        .await;
13543}
13544
13545#[gpui::test]
13546async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
13547    init_test(cx, |_| {});
13548
13549    let mut cx = EditorLspTestContext::new_rust(
13550        lsp::ServerCapabilities {
13551            signature_help_provider: Some(lsp::SignatureHelpOptions {
13552                ..Default::default()
13553            }),
13554            ..Default::default()
13555        },
13556        cx,
13557    )
13558    .await;
13559
13560    cx.set_state(indoc! {"
13561        fn main() {
13562            overloadedˇ
13563        }
13564    "});
13565
13566    cx.update_editor(|editor, window, cx| {
13567        editor.handle_input("(", window, cx);
13568        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13569    });
13570
13571    // Mock response with 3 signatures
13572    let mocked_response = lsp::SignatureHelp {
13573        signatures: vec![
13574            lsp::SignatureInformation {
13575                label: "fn overloaded(x: i32)".to_string(),
13576                documentation: None,
13577                parameters: Some(vec![lsp::ParameterInformation {
13578                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13579                    documentation: None,
13580                }]),
13581                active_parameter: None,
13582            },
13583            lsp::SignatureInformation {
13584                label: "fn overloaded(x: i32, y: i32)".to_string(),
13585                documentation: None,
13586                parameters: Some(vec![
13587                    lsp::ParameterInformation {
13588                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13589                        documentation: None,
13590                    },
13591                    lsp::ParameterInformation {
13592                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13593                        documentation: None,
13594                    },
13595                ]),
13596                active_parameter: None,
13597            },
13598            lsp::SignatureInformation {
13599                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
13600                documentation: None,
13601                parameters: Some(vec![
13602                    lsp::ParameterInformation {
13603                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13604                        documentation: None,
13605                    },
13606                    lsp::ParameterInformation {
13607                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13608                        documentation: None,
13609                    },
13610                    lsp::ParameterInformation {
13611                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
13612                        documentation: None,
13613                    },
13614                ]),
13615                active_parameter: None,
13616            },
13617        ],
13618        active_signature: Some(1),
13619        active_parameter: Some(0),
13620    };
13621    handle_signature_help_request(&mut cx, mocked_response).await;
13622
13623    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13624        .await;
13625
13626    // Verify we have multiple signatures and the right one is selected
13627    cx.editor(|editor, _, _| {
13628        let popover = editor.signature_help_state.popover().cloned().unwrap();
13629        assert_eq!(popover.signatures.len(), 3);
13630        // active_signature was 1, so that should be the current
13631        assert_eq!(popover.current_signature, 1);
13632        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
13633        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
13634        assert_eq!(
13635            popover.signatures[2].label,
13636            "fn overloaded(x: i32, y: i32, z: i32)"
13637        );
13638    });
13639
13640    // Test navigation functionality
13641    cx.update_editor(|editor, window, cx| {
13642        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13643    });
13644
13645    cx.editor(|editor, _, _| {
13646        let popover = editor.signature_help_state.popover().cloned().unwrap();
13647        assert_eq!(popover.current_signature, 2);
13648    });
13649
13650    // Test wrap around
13651    cx.update_editor(|editor, window, cx| {
13652        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13653    });
13654
13655    cx.editor(|editor, _, _| {
13656        let popover = editor.signature_help_state.popover().cloned().unwrap();
13657        assert_eq!(popover.current_signature, 0);
13658    });
13659
13660    // Test previous navigation
13661    cx.update_editor(|editor, window, cx| {
13662        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
13663    });
13664
13665    cx.editor(|editor, _, _| {
13666        let popover = editor.signature_help_state.popover().cloned().unwrap();
13667        assert_eq!(popover.current_signature, 2);
13668    });
13669}
13670
13671#[gpui::test]
13672async fn test_completion_mode(cx: &mut TestAppContext) {
13673    init_test(cx, |_| {});
13674    let mut cx = EditorLspTestContext::new_rust(
13675        lsp::ServerCapabilities {
13676            completion_provider: Some(lsp::CompletionOptions {
13677                resolve_provider: Some(true),
13678                ..Default::default()
13679            }),
13680            ..Default::default()
13681        },
13682        cx,
13683    )
13684    .await;
13685
13686    struct Run {
13687        run_description: &'static str,
13688        initial_state: String,
13689        buffer_marked_text: String,
13690        completion_label: &'static str,
13691        completion_text: &'static str,
13692        expected_with_insert_mode: String,
13693        expected_with_replace_mode: String,
13694        expected_with_replace_subsequence_mode: String,
13695        expected_with_replace_suffix_mode: String,
13696    }
13697
13698    let runs = [
13699        Run {
13700            run_description: "Start of word matches completion text",
13701            initial_state: "before ediˇ after".into(),
13702            buffer_marked_text: "before <edi|> after".into(),
13703            completion_label: "editor",
13704            completion_text: "editor",
13705            expected_with_insert_mode: "before editorˇ after".into(),
13706            expected_with_replace_mode: "before editorˇ after".into(),
13707            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13708            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13709        },
13710        Run {
13711            run_description: "Accept same text at the middle of the word",
13712            initial_state: "before ediˇtor after".into(),
13713            buffer_marked_text: "before <edi|tor> after".into(),
13714            completion_label: "editor",
13715            completion_text: "editor",
13716            expected_with_insert_mode: "before editorˇtor after".into(),
13717            expected_with_replace_mode: "before editorˇ after".into(),
13718            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13719            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13720        },
13721        Run {
13722            run_description: "End of word matches completion text -- cursor at end",
13723            initial_state: "before torˇ after".into(),
13724            buffer_marked_text: "before <tor|> after".into(),
13725            completion_label: "editor",
13726            completion_text: "editor",
13727            expected_with_insert_mode: "before editorˇ after".into(),
13728            expected_with_replace_mode: "before editorˇ after".into(),
13729            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13730            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13731        },
13732        Run {
13733            run_description: "End of word matches completion text -- cursor at start",
13734            initial_state: "before ˇtor after".into(),
13735            buffer_marked_text: "before <|tor> after".into(),
13736            completion_label: "editor",
13737            completion_text: "editor",
13738            expected_with_insert_mode: "before editorˇtor after".into(),
13739            expected_with_replace_mode: "before editorˇ after".into(),
13740            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13741            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13742        },
13743        Run {
13744            run_description: "Prepend text containing whitespace",
13745            initial_state: "pˇfield: bool".into(),
13746            buffer_marked_text: "<p|field>: bool".into(),
13747            completion_label: "pub ",
13748            completion_text: "pub ",
13749            expected_with_insert_mode: "pub ˇfield: bool".into(),
13750            expected_with_replace_mode: "pub ˇ: bool".into(),
13751            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
13752            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
13753        },
13754        Run {
13755            run_description: "Add element to start of list",
13756            initial_state: "[element_ˇelement_2]".into(),
13757            buffer_marked_text: "[<element_|element_2>]".into(),
13758            completion_label: "element_1",
13759            completion_text: "element_1",
13760            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
13761            expected_with_replace_mode: "[element_1ˇ]".into(),
13762            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
13763            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
13764        },
13765        Run {
13766            run_description: "Add element to start of list -- first and second elements are equal",
13767            initial_state: "[elˇelement]".into(),
13768            buffer_marked_text: "[<el|element>]".into(),
13769            completion_label: "element",
13770            completion_text: "element",
13771            expected_with_insert_mode: "[elementˇelement]".into(),
13772            expected_with_replace_mode: "[elementˇ]".into(),
13773            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
13774            expected_with_replace_suffix_mode: "[elementˇ]".into(),
13775        },
13776        Run {
13777            run_description: "Ends with matching suffix",
13778            initial_state: "SubˇError".into(),
13779            buffer_marked_text: "<Sub|Error>".into(),
13780            completion_label: "SubscriptionError",
13781            completion_text: "SubscriptionError",
13782            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
13783            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13784            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13785            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
13786        },
13787        Run {
13788            run_description: "Suffix is a subsequence -- contiguous",
13789            initial_state: "SubˇErr".into(),
13790            buffer_marked_text: "<Sub|Err>".into(),
13791            completion_label: "SubscriptionError",
13792            completion_text: "SubscriptionError",
13793            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
13794            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13795            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13796            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
13797        },
13798        Run {
13799            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
13800            initial_state: "Suˇscrirr".into(),
13801            buffer_marked_text: "<Su|scrirr>".into(),
13802            completion_label: "SubscriptionError",
13803            completion_text: "SubscriptionError",
13804            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
13805            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13806            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13807            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
13808        },
13809        Run {
13810            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
13811            initial_state: "foo(indˇix)".into(),
13812            buffer_marked_text: "foo(<ind|ix>)".into(),
13813            completion_label: "node_index",
13814            completion_text: "node_index",
13815            expected_with_insert_mode: "foo(node_indexˇix)".into(),
13816            expected_with_replace_mode: "foo(node_indexˇ)".into(),
13817            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
13818            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
13819        },
13820        Run {
13821            run_description: "Replace range ends before cursor - should extend to cursor",
13822            initial_state: "before editˇo after".into(),
13823            buffer_marked_text: "before <{ed}>it|o after".into(),
13824            completion_label: "editor",
13825            completion_text: "editor",
13826            expected_with_insert_mode: "before editorˇo after".into(),
13827            expected_with_replace_mode: "before editorˇo after".into(),
13828            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
13829            expected_with_replace_suffix_mode: "before editorˇo after".into(),
13830        },
13831        Run {
13832            run_description: "Uses label for suffix matching",
13833            initial_state: "before ediˇtor after".into(),
13834            buffer_marked_text: "before <edi|tor> after".into(),
13835            completion_label: "editor",
13836            completion_text: "editor()",
13837            expected_with_insert_mode: "before editor()ˇtor after".into(),
13838            expected_with_replace_mode: "before editor()ˇ after".into(),
13839            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
13840            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
13841        },
13842        Run {
13843            run_description: "Case insensitive subsequence and suffix matching",
13844            initial_state: "before EDiˇtoR after".into(),
13845            buffer_marked_text: "before <EDi|toR> after".into(),
13846            completion_label: "editor",
13847            completion_text: "editor",
13848            expected_with_insert_mode: "before editorˇtoR after".into(),
13849            expected_with_replace_mode: "before editorˇ after".into(),
13850            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13851            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13852        },
13853    ];
13854
13855    for run in runs {
13856        let run_variations = [
13857            (LspInsertMode::Insert, run.expected_with_insert_mode),
13858            (LspInsertMode::Replace, run.expected_with_replace_mode),
13859            (
13860                LspInsertMode::ReplaceSubsequence,
13861                run.expected_with_replace_subsequence_mode,
13862            ),
13863            (
13864                LspInsertMode::ReplaceSuffix,
13865                run.expected_with_replace_suffix_mode,
13866            ),
13867        ];
13868
13869        for (lsp_insert_mode, expected_text) in run_variations {
13870            eprintln!(
13871                "run = {:?}, mode = {lsp_insert_mode:.?}",
13872                run.run_description,
13873            );
13874
13875            update_test_language_settings(&mut cx, |settings| {
13876                settings.defaults.completions = Some(CompletionSettingsContent {
13877                    lsp_insert_mode: Some(lsp_insert_mode),
13878                    words: Some(WordsCompletionMode::Disabled),
13879                    words_min_length: Some(0),
13880                    ..Default::default()
13881                });
13882            });
13883
13884            cx.set_state(&run.initial_state);
13885            cx.update_editor(|editor, window, cx| {
13886                editor.show_completions(&ShowCompletions, window, cx);
13887            });
13888
13889            let counter = Arc::new(AtomicUsize::new(0));
13890            handle_completion_request_with_insert_and_replace(
13891                &mut cx,
13892                &run.buffer_marked_text,
13893                vec![(run.completion_label, run.completion_text)],
13894                counter.clone(),
13895            )
13896            .await;
13897            cx.condition(|editor, _| editor.context_menu_visible())
13898                .await;
13899            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13900
13901            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13902                editor
13903                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
13904                    .unwrap()
13905            });
13906            cx.assert_editor_state(&expected_text);
13907            handle_resolve_completion_request(&mut cx, None).await;
13908            apply_additional_edits.await.unwrap();
13909        }
13910    }
13911}
13912
13913#[gpui::test]
13914async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
13915    init_test(cx, |_| {});
13916    let mut cx = EditorLspTestContext::new_rust(
13917        lsp::ServerCapabilities {
13918            completion_provider: Some(lsp::CompletionOptions {
13919                resolve_provider: Some(true),
13920                ..Default::default()
13921            }),
13922            ..Default::default()
13923        },
13924        cx,
13925    )
13926    .await;
13927
13928    let initial_state = "SubˇError";
13929    let buffer_marked_text = "<Sub|Error>";
13930    let completion_text = "SubscriptionError";
13931    let expected_with_insert_mode = "SubscriptionErrorˇError";
13932    let expected_with_replace_mode = "SubscriptionErrorˇ";
13933
13934    update_test_language_settings(&mut cx, |settings| {
13935        settings.defaults.completions = Some(CompletionSettingsContent {
13936            words: Some(WordsCompletionMode::Disabled),
13937            words_min_length: Some(0),
13938            // set the opposite here to ensure that the action is overriding the default behavior
13939            lsp_insert_mode: Some(LspInsertMode::Insert),
13940            ..Default::default()
13941        });
13942    });
13943
13944    cx.set_state(initial_state);
13945    cx.update_editor(|editor, window, cx| {
13946        editor.show_completions(&ShowCompletions, window, cx);
13947    });
13948
13949    let counter = Arc::new(AtomicUsize::new(0));
13950    handle_completion_request_with_insert_and_replace(
13951        &mut cx,
13952        buffer_marked_text,
13953        vec![(completion_text, completion_text)],
13954        counter.clone(),
13955    )
13956    .await;
13957    cx.condition(|editor, _| editor.context_menu_visible())
13958        .await;
13959    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13960
13961    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13962        editor
13963            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13964            .unwrap()
13965    });
13966    cx.assert_editor_state(expected_with_replace_mode);
13967    handle_resolve_completion_request(&mut cx, None).await;
13968    apply_additional_edits.await.unwrap();
13969
13970    update_test_language_settings(&mut cx, |settings| {
13971        settings.defaults.completions = Some(CompletionSettingsContent {
13972            words: Some(WordsCompletionMode::Disabled),
13973            words_min_length: Some(0),
13974            // set the opposite here to ensure that the action is overriding the default behavior
13975            lsp_insert_mode: Some(LspInsertMode::Replace),
13976            ..Default::default()
13977        });
13978    });
13979
13980    cx.set_state(initial_state);
13981    cx.update_editor(|editor, window, cx| {
13982        editor.show_completions(&ShowCompletions, window, cx);
13983    });
13984    handle_completion_request_with_insert_and_replace(
13985        &mut cx,
13986        buffer_marked_text,
13987        vec![(completion_text, completion_text)],
13988        counter.clone(),
13989    )
13990    .await;
13991    cx.condition(|editor, _| editor.context_menu_visible())
13992        .await;
13993    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
13994
13995    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13996        editor
13997            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
13998            .unwrap()
13999    });
14000    cx.assert_editor_state(expected_with_insert_mode);
14001    handle_resolve_completion_request(&mut cx, None).await;
14002    apply_additional_edits.await.unwrap();
14003}
14004
14005#[gpui::test]
14006async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
14007    init_test(cx, |_| {});
14008    let mut cx = EditorLspTestContext::new_rust(
14009        lsp::ServerCapabilities {
14010            completion_provider: Some(lsp::CompletionOptions {
14011                resolve_provider: Some(true),
14012                ..Default::default()
14013            }),
14014            ..Default::default()
14015        },
14016        cx,
14017    )
14018    .await;
14019
14020    // scenario: surrounding text matches completion text
14021    let completion_text = "to_offset";
14022    let initial_state = indoc! {"
14023        1. buf.to_offˇsuffix
14024        2. buf.to_offˇsuf
14025        3. buf.to_offˇfix
14026        4. buf.to_offˇ
14027        5. into_offˇensive
14028        6. ˇsuffix
14029        7. let ˇ //
14030        8. aaˇzz
14031        9. buf.to_off«zzzzzˇ»suffix
14032        10. buf.«ˇzzzzz»suffix
14033        11. to_off«ˇzzzzz»
14034
14035        buf.to_offˇsuffix  // newest cursor
14036    "};
14037    let completion_marked_buffer = indoc! {"
14038        1. buf.to_offsuffix
14039        2. buf.to_offsuf
14040        3. buf.to_offfix
14041        4. buf.to_off
14042        5. into_offensive
14043        6. suffix
14044        7. let  //
14045        8. aazz
14046        9. buf.to_offzzzzzsuffix
14047        10. buf.zzzzzsuffix
14048        11. to_offzzzzz
14049
14050        buf.<to_off|suffix>  // newest cursor
14051    "};
14052    let expected = indoc! {"
14053        1. buf.to_offsetˇ
14054        2. buf.to_offsetˇsuf
14055        3. buf.to_offsetˇfix
14056        4. buf.to_offsetˇ
14057        5. into_offsetˇensive
14058        6. to_offsetˇsuffix
14059        7. let to_offsetˇ //
14060        8. aato_offsetˇzz
14061        9. buf.to_offsetˇ
14062        10. buf.to_offsetˇsuffix
14063        11. to_offsetˇ
14064
14065        buf.to_offsetˇ  // newest cursor
14066    "};
14067    cx.set_state(initial_state);
14068    cx.update_editor(|editor, window, cx| {
14069        editor.show_completions(&ShowCompletions, window, cx);
14070    });
14071    handle_completion_request_with_insert_and_replace(
14072        &mut cx,
14073        completion_marked_buffer,
14074        vec![(completion_text, completion_text)],
14075        Arc::new(AtomicUsize::new(0)),
14076    )
14077    .await;
14078    cx.condition(|editor, _| editor.context_menu_visible())
14079        .await;
14080    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14081        editor
14082            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14083            .unwrap()
14084    });
14085    cx.assert_editor_state(expected);
14086    handle_resolve_completion_request(&mut cx, None).await;
14087    apply_additional_edits.await.unwrap();
14088
14089    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
14090    let completion_text = "foo_and_bar";
14091    let initial_state = indoc! {"
14092        1. ooanbˇ
14093        2. zooanbˇ
14094        3. ooanbˇz
14095        4. zooanbˇz
14096        5. ooanˇ
14097        6. oanbˇ
14098
14099        ooanbˇ
14100    "};
14101    let completion_marked_buffer = indoc! {"
14102        1. ooanb
14103        2. zooanb
14104        3. ooanbz
14105        4. zooanbz
14106        5. ooan
14107        6. oanb
14108
14109        <ooanb|>
14110    "};
14111    let expected = indoc! {"
14112        1. foo_and_barˇ
14113        2. zfoo_and_barˇ
14114        3. foo_and_barˇz
14115        4. zfoo_and_barˇz
14116        5. ooanfoo_and_barˇ
14117        6. oanbfoo_and_barˇ
14118
14119        foo_and_barˇ
14120    "};
14121    cx.set_state(initial_state);
14122    cx.update_editor(|editor, window, cx| {
14123        editor.show_completions(&ShowCompletions, window, cx);
14124    });
14125    handle_completion_request_with_insert_and_replace(
14126        &mut cx,
14127        completion_marked_buffer,
14128        vec![(completion_text, completion_text)],
14129        Arc::new(AtomicUsize::new(0)),
14130    )
14131    .await;
14132    cx.condition(|editor, _| editor.context_menu_visible())
14133        .await;
14134    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14135        editor
14136            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14137            .unwrap()
14138    });
14139    cx.assert_editor_state(expected);
14140    handle_resolve_completion_request(&mut cx, None).await;
14141    apply_additional_edits.await.unwrap();
14142
14143    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
14144    // (expects the same as if it was inserted at the end)
14145    let completion_text = "foo_and_bar";
14146    let initial_state = indoc! {"
14147        1. ooˇanb
14148        2. zooˇanb
14149        3. ooˇanbz
14150        4. zooˇanbz
14151
14152        ooˇanb
14153    "};
14154    let completion_marked_buffer = indoc! {"
14155        1. ooanb
14156        2. zooanb
14157        3. ooanbz
14158        4. zooanbz
14159
14160        <oo|anb>
14161    "};
14162    let expected = indoc! {"
14163        1. foo_and_barˇ
14164        2. zfoo_and_barˇ
14165        3. foo_and_barˇz
14166        4. zfoo_and_barˇz
14167
14168        foo_and_barˇ
14169    "};
14170    cx.set_state(initial_state);
14171    cx.update_editor(|editor, window, cx| {
14172        editor.show_completions(&ShowCompletions, window, cx);
14173    });
14174    handle_completion_request_with_insert_and_replace(
14175        &mut cx,
14176        completion_marked_buffer,
14177        vec![(completion_text, completion_text)],
14178        Arc::new(AtomicUsize::new(0)),
14179    )
14180    .await;
14181    cx.condition(|editor, _| editor.context_menu_visible())
14182        .await;
14183    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14184        editor
14185            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14186            .unwrap()
14187    });
14188    cx.assert_editor_state(expected);
14189    handle_resolve_completion_request(&mut cx, None).await;
14190    apply_additional_edits.await.unwrap();
14191}
14192
14193// This used to crash
14194#[gpui::test]
14195async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
14196    init_test(cx, |_| {});
14197
14198    let buffer_text = indoc! {"
14199        fn main() {
14200            10.satu;
14201
14202            //
14203            // separate cursors so they open in different excerpts (manually reproducible)
14204            //
14205
14206            10.satu20;
14207        }
14208    "};
14209    let multibuffer_text_with_selections = indoc! {"
14210        fn main() {
14211            10.satuˇ;
14212
14213            //
14214
14215            //
14216
14217            10.satuˇ20;
14218        }
14219    "};
14220    let expected_multibuffer = indoc! {"
14221        fn main() {
14222            10.saturating_sub()ˇ;
14223
14224            //
14225
14226            //
14227
14228            10.saturating_sub()ˇ;
14229        }
14230    "};
14231
14232    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
14233    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
14234
14235    let fs = FakeFs::new(cx.executor());
14236    fs.insert_tree(
14237        path!("/a"),
14238        json!({
14239            "main.rs": buffer_text,
14240        }),
14241    )
14242    .await;
14243
14244    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14245    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14246    language_registry.add(rust_lang());
14247    let mut fake_servers = language_registry.register_fake_lsp(
14248        "Rust",
14249        FakeLspAdapter {
14250            capabilities: lsp::ServerCapabilities {
14251                completion_provider: Some(lsp::CompletionOptions {
14252                    resolve_provider: None,
14253                    ..lsp::CompletionOptions::default()
14254                }),
14255                ..lsp::ServerCapabilities::default()
14256            },
14257            ..FakeLspAdapter::default()
14258        },
14259    );
14260    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14261    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14262    let buffer = project
14263        .update(cx, |project, cx| {
14264            project.open_local_buffer(path!("/a/main.rs"), cx)
14265        })
14266        .await
14267        .unwrap();
14268
14269    let multi_buffer = cx.new(|cx| {
14270        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
14271        multi_buffer.push_excerpts(
14272            buffer.clone(),
14273            [ExcerptRange::new(0..first_excerpt_end)],
14274            cx,
14275        );
14276        multi_buffer.push_excerpts(
14277            buffer.clone(),
14278            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
14279            cx,
14280        );
14281        multi_buffer
14282    });
14283
14284    let editor = workspace
14285        .update(cx, |_, window, cx| {
14286            cx.new(|cx| {
14287                Editor::new(
14288                    EditorMode::Full {
14289                        scale_ui_elements_with_buffer_font_size: false,
14290                        show_active_line_background: false,
14291                        sizing_behavior: SizingBehavior::Default,
14292                    },
14293                    multi_buffer.clone(),
14294                    Some(project.clone()),
14295                    window,
14296                    cx,
14297                )
14298            })
14299        })
14300        .unwrap();
14301
14302    let pane = workspace
14303        .update(cx, |workspace, _, _| workspace.active_pane().clone())
14304        .unwrap();
14305    pane.update_in(cx, |pane, window, cx| {
14306        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
14307    });
14308
14309    let fake_server = fake_servers.next().await.unwrap();
14310
14311    editor.update_in(cx, |editor, window, cx| {
14312        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14313            s.select_ranges([
14314                Point::new(1, 11)..Point::new(1, 11),
14315                Point::new(7, 11)..Point::new(7, 11),
14316            ])
14317        });
14318
14319        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
14320    });
14321
14322    editor.update_in(cx, |editor, window, cx| {
14323        editor.show_completions(&ShowCompletions, window, cx);
14324    });
14325
14326    fake_server
14327        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14328            let completion_item = lsp::CompletionItem {
14329                label: "saturating_sub()".into(),
14330                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
14331                    lsp::InsertReplaceEdit {
14332                        new_text: "saturating_sub()".to_owned(),
14333                        insert: lsp::Range::new(
14334                            lsp::Position::new(7, 7),
14335                            lsp::Position::new(7, 11),
14336                        ),
14337                        replace: lsp::Range::new(
14338                            lsp::Position::new(7, 7),
14339                            lsp::Position::new(7, 13),
14340                        ),
14341                    },
14342                )),
14343                ..lsp::CompletionItem::default()
14344            };
14345
14346            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
14347        })
14348        .next()
14349        .await
14350        .unwrap();
14351
14352    cx.condition(&editor, |editor, _| editor.context_menu_visible())
14353        .await;
14354
14355    editor
14356        .update_in(cx, |editor, window, cx| {
14357            editor
14358                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14359                .unwrap()
14360        })
14361        .await
14362        .unwrap();
14363
14364    editor.update(cx, |editor, cx| {
14365        assert_text_with_selections(editor, expected_multibuffer, cx);
14366    })
14367}
14368
14369#[gpui::test]
14370async fn test_completion(cx: &mut TestAppContext) {
14371    init_test(cx, |_| {});
14372
14373    let mut cx = EditorLspTestContext::new_rust(
14374        lsp::ServerCapabilities {
14375            completion_provider: Some(lsp::CompletionOptions {
14376                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14377                resolve_provider: Some(true),
14378                ..Default::default()
14379            }),
14380            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14381            ..Default::default()
14382        },
14383        cx,
14384    )
14385    .await;
14386    let counter = Arc::new(AtomicUsize::new(0));
14387
14388    cx.set_state(indoc! {"
14389        oneˇ
14390        two
14391        three
14392    "});
14393    cx.simulate_keystroke(".");
14394    handle_completion_request(
14395        indoc! {"
14396            one.|<>
14397            two
14398            three
14399        "},
14400        vec!["first_completion", "second_completion"],
14401        true,
14402        counter.clone(),
14403        &mut cx,
14404    )
14405    .await;
14406    cx.condition(|editor, _| editor.context_menu_visible())
14407        .await;
14408    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14409
14410    let _handler = handle_signature_help_request(
14411        &mut cx,
14412        lsp::SignatureHelp {
14413            signatures: vec![lsp::SignatureInformation {
14414                label: "test signature".to_string(),
14415                documentation: None,
14416                parameters: Some(vec![lsp::ParameterInformation {
14417                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
14418                    documentation: None,
14419                }]),
14420                active_parameter: None,
14421            }],
14422            active_signature: None,
14423            active_parameter: None,
14424        },
14425    );
14426    cx.update_editor(|editor, window, cx| {
14427        assert!(
14428            !editor.signature_help_state.is_shown(),
14429            "No signature help was called for"
14430        );
14431        editor.show_signature_help(&ShowSignatureHelp, window, cx);
14432    });
14433    cx.run_until_parked();
14434    cx.update_editor(|editor, _, _| {
14435        assert!(
14436            !editor.signature_help_state.is_shown(),
14437            "No signature help should be shown when completions menu is open"
14438        );
14439    });
14440
14441    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14442        editor.context_menu_next(&Default::default(), window, cx);
14443        editor
14444            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14445            .unwrap()
14446    });
14447    cx.assert_editor_state(indoc! {"
14448        one.second_completionˇ
14449        two
14450        three
14451    "});
14452
14453    handle_resolve_completion_request(
14454        &mut cx,
14455        Some(vec![
14456            (
14457                //This overlaps with the primary completion edit which is
14458                //misbehavior from the LSP spec, test that we filter it out
14459                indoc! {"
14460                    one.second_ˇcompletion
14461                    two
14462                    threeˇ
14463                "},
14464                "overlapping additional edit",
14465            ),
14466            (
14467                indoc! {"
14468                    one.second_completion
14469                    two
14470                    threeˇ
14471                "},
14472                "\nadditional edit",
14473            ),
14474        ]),
14475    )
14476    .await;
14477    apply_additional_edits.await.unwrap();
14478    cx.assert_editor_state(indoc! {"
14479        one.second_completionˇ
14480        two
14481        three
14482        additional edit
14483    "});
14484
14485    cx.set_state(indoc! {"
14486        one.second_completion
14487        twoˇ
14488        threeˇ
14489        additional edit
14490    "});
14491    cx.simulate_keystroke(" ");
14492    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14493    cx.simulate_keystroke("s");
14494    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14495
14496    cx.assert_editor_state(indoc! {"
14497        one.second_completion
14498        two sˇ
14499        three sˇ
14500        additional edit
14501    "});
14502    handle_completion_request(
14503        indoc! {"
14504            one.second_completion
14505            two s
14506            three <s|>
14507            additional edit
14508        "},
14509        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14510        true,
14511        counter.clone(),
14512        &mut cx,
14513    )
14514    .await;
14515    cx.condition(|editor, _| editor.context_menu_visible())
14516        .await;
14517    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14518
14519    cx.simulate_keystroke("i");
14520
14521    handle_completion_request(
14522        indoc! {"
14523            one.second_completion
14524            two si
14525            three <si|>
14526            additional edit
14527        "},
14528        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14529        true,
14530        counter.clone(),
14531        &mut cx,
14532    )
14533    .await;
14534    cx.condition(|editor, _| editor.context_menu_visible())
14535        .await;
14536    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14537
14538    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14539        editor
14540            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14541            .unwrap()
14542    });
14543    cx.assert_editor_state(indoc! {"
14544        one.second_completion
14545        two sixth_completionˇ
14546        three sixth_completionˇ
14547        additional edit
14548    "});
14549
14550    apply_additional_edits.await.unwrap();
14551
14552    update_test_language_settings(&mut cx, |settings| {
14553        settings.defaults.show_completions_on_input = Some(false);
14554    });
14555    cx.set_state("editorˇ");
14556    cx.simulate_keystroke(".");
14557    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14558    cx.simulate_keystrokes("c l o");
14559    cx.assert_editor_state("editor.cloˇ");
14560    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14561    cx.update_editor(|editor, window, cx| {
14562        editor.show_completions(&ShowCompletions, window, cx);
14563    });
14564    handle_completion_request(
14565        "editor.<clo|>",
14566        vec!["close", "clobber"],
14567        true,
14568        counter.clone(),
14569        &mut cx,
14570    )
14571    .await;
14572    cx.condition(|editor, _| editor.context_menu_visible())
14573        .await;
14574    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
14575
14576    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14577        editor
14578            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14579            .unwrap()
14580    });
14581    cx.assert_editor_state("editor.clobberˇ");
14582    handle_resolve_completion_request(&mut cx, None).await;
14583    apply_additional_edits.await.unwrap();
14584}
14585
14586#[gpui::test]
14587async fn test_completion_reuse(cx: &mut TestAppContext) {
14588    init_test(cx, |_| {});
14589
14590    let mut cx = EditorLspTestContext::new_rust(
14591        lsp::ServerCapabilities {
14592            completion_provider: Some(lsp::CompletionOptions {
14593                trigger_characters: Some(vec![".".to_string()]),
14594                ..Default::default()
14595            }),
14596            ..Default::default()
14597        },
14598        cx,
14599    )
14600    .await;
14601
14602    let counter = Arc::new(AtomicUsize::new(0));
14603    cx.set_state("objˇ");
14604    cx.simulate_keystroke(".");
14605
14606    // Initial completion request returns complete results
14607    let is_incomplete = false;
14608    handle_completion_request(
14609        "obj.|<>",
14610        vec!["a", "ab", "abc"],
14611        is_incomplete,
14612        counter.clone(),
14613        &mut cx,
14614    )
14615    .await;
14616    cx.run_until_parked();
14617    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14618    cx.assert_editor_state("obj.ˇ");
14619    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14620
14621    // Type "a" - filters existing completions
14622    cx.simulate_keystroke("a");
14623    cx.run_until_parked();
14624    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14625    cx.assert_editor_state("obj.aˇ");
14626    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14627
14628    // Type "b" - filters existing completions
14629    cx.simulate_keystroke("b");
14630    cx.run_until_parked();
14631    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14632    cx.assert_editor_state("obj.abˇ");
14633    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14634
14635    // Type "c" - filters existing completions
14636    cx.simulate_keystroke("c");
14637    cx.run_until_parked();
14638    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14639    cx.assert_editor_state("obj.abcˇ");
14640    check_displayed_completions(vec!["abc"], &mut cx);
14641
14642    // Backspace to delete "c" - filters existing completions
14643    cx.update_editor(|editor, window, cx| {
14644        editor.backspace(&Backspace, window, cx);
14645    });
14646    cx.run_until_parked();
14647    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14648    cx.assert_editor_state("obj.abˇ");
14649    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14650
14651    // Moving cursor to the left dismisses menu.
14652    cx.update_editor(|editor, window, cx| {
14653        editor.move_left(&MoveLeft, window, cx);
14654    });
14655    cx.run_until_parked();
14656    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14657    cx.assert_editor_state("obj.aˇb");
14658    cx.update_editor(|editor, _, _| {
14659        assert_eq!(editor.context_menu_visible(), false);
14660    });
14661
14662    // Type "b" - new request
14663    cx.simulate_keystroke("b");
14664    let is_incomplete = false;
14665    handle_completion_request(
14666        "obj.<ab|>a",
14667        vec!["ab", "abc"],
14668        is_incomplete,
14669        counter.clone(),
14670        &mut cx,
14671    )
14672    .await;
14673    cx.run_until_parked();
14674    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14675    cx.assert_editor_state("obj.abˇb");
14676    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14677
14678    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
14679    cx.update_editor(|editor, window, cx| {
14680        editor.backspace(&Backspace, window, cx);
14681    });
14682    let is_incomplete = false;
14683    handle_completion_request(
14684        "obj.<a|>b",
14685        vec!["a", "ab", "abc"],
14686        is_incomplete,
14687        counter.clone(),
14688        &mut cx,
14689    )
14690    .await;
14691    cx.run_until_parked();
14692    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14693    cx.assert_editor_state("obj.aˇb");
14694    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14695
14696    // Backspace to delete "a" - dismisses menu.
14697    cx.update_editor(|editor, window, cx| {
14698        editor.backspace(&Backspace, window, cx);
14699    });
14700    cx.run_until_parked();
14701    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14702    cx.assert_editor_state("obj.ˇb");
14703    cx.update_editor(|editor, _, _| {
14704        assert_eq!(editor.context_menu_visible(), false);
14705    });
14706}
14707
14708#[gpui::test]
14709async fn test_word_completion(cx: &mut TestAppContext) {
14710    let lsp_fetch_timeout_ms = 10;
14711    init_test(cx, |language_settings| {
14712        language_settings.defaults.completions = Some(CompletionSettingsContent {
14713            words_min_length: Some(0),
14714            lsp_fetch_timeout_ms: Some(10),
14715            lsp_insert_mode: Some(LspInsertMode::Insert),
14716            ..Default::default()
14717        });
14718    });
14719
14720    let mut cx = EditorLspTestContext::new_rust(
14721        lsp::ServerCapabilities {
14722            completion_provider: Some(lsp::CompletionOptions {
14723                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14724                ..lsp::CompletionOptions::default()
14725            }),
14726            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14727            ..lsp::ServerCapabilities::default()
14728        },
14729        cx,
14730    )
14731    .await;
14732
14733    let throttle_completions = Arc::new(AtomicBool::new(false));
14734
14735    let lsp_throttle_completions = throttle_completions.clone();
14736    let _completion_requests_handler =
14737        cx.lsp
14738            .server
14739            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
14740                let lsp_throttle_completions = lsp_throttle_completions.clone();
14741                let cx = cx.clone();
14742                async move {
14743                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
14744                        cx.background_executor()
14745                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
14746                            .await;
14747                    }
14748                    Ok(Some(lsp::CompletionResponse::Array(vec![
14749                        lsp::CompletionItem {
14750                            label: "first".into(),
14751                            ..lsp::CompletionItem::default()
14752                        },
14753                        lsp::CompletionItem {
14754                            label: "last".into(),
14755                            ..lsp::CompletionItem::default()
14756                        },
14757                    ])))
14758                }
14759            });
14760
14761    cx.set_state(indoc! {"
14762        oneˇ
14763        two
14764        three
14765    "});
14766    cx.simulate_keystroke(".");
14767    cx.executor().run_until_parked();
14768    cx.condition(|editor, _| editor.context_menu_visible())
14769        .await;
14770    cx.update_editor(|editor, window, cx| {
14771        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14772        {
14773            assert_eq!(
14774                completion_menu_entries(menu),
14775                &["first", "last"],
14776                "When LSP server is fast to reply, no fallback word completions are used"
14777            );
14778        } else {
14779            panic!("expected completion menu to be open");
14780        }
14781        editor.cancel(&Cancel, window, cx);
14782    });
14783    cx.executor().run_until_parked();
14784    cx.condition(|editor, _| !editor.context_menu_visible())
14785        .await;
14786
14787    throttle_completions.store(true, atomic::Ordering::Release);
14788    cx.simulate_keystroke(".");
14789    cx.executor()
14790        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
14791    cx.executor().run_until_parked();
14792    cx.condition(|editor, _| editor.context_menu_visible())
14793        .await;
14794    cx.update_editor(|editor, _, _| {
14795        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14796        {
14797            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
14798                "When LSP server is slow, document words can be shown instead, if configured accordingly");
14799        } else {
14800            panic!("expected completion menu to be open");
14801        }
14802    });
14803}
14804
14805#[gpui::test]
14806async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
14807    init_test(cx, |language_settings| {
14808        language_settings.defaults.completions = Some(CompletionSettingsContent {
14809            words: Some(WordsCompletionMode::Enabled),
14810            words_min_length: Some(0),
14811            lsp_insert_mode: Some(LspInsertMode::Insert),
14812            ..Default::default()
14813        });
14814    });
14815
14816    let mut cx = EditorLspTestContext::new_rust(
14817        lsp::ServerCapabilities {
14818            completion_provider: Some(lsp::CompletionOptions {
14819                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14820                ..lsp::CompletionOptions::default()
14821            }),
14822            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14823            ..lsp::ServerCapabilities::default()
14824        },
14825        cx,
14826    )
14827    .await;
14828
14829    let _completion_requests_handler =
14830        cx.lsp
14831            .server
14832            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14833                Ok(Some(lsp::CompletionResponse::Array(vec![
14834                    lsp::CompletionItem {
14835                        label: "first".into(),
14836                        ..lsp::CompletionItem::default()
14837                    },
14838                    lsp::CompletionItem {
14839                        label: "last".into(),
14840                        ..lsp::CompletionItem::default()
14841                    },
14842                ])))
14843            });
14844
14845    cx.set_state(indoc! {"ˇ
14846        first
14847        last
14848        second
14849    "});
14850    cx.simulate_keystroke(".");
14851    cx.executor().run_until_parked();
14852    cx.condition(|editor, _| editor.context_menu_visible())
14853        .await;
14854    cx.update_editor(|editor, _, _| {
14855        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14856        {
14857            assert_eq!(
14858                completion_menu_entries(menu),
14859                &["first", "last", "second"],
14860                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
14861            );
14862        } else {
14863            panic!("expected completion menu to be open");
14864        }
14865    });
14866}
14867
14868#[gpui::test]
14869async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
14870    init_test(cx, |language_settings| {
14871        language_settings.defaults.completions = Some(CompletionSettingsContent {
14872            words: Some(WordsCompletionMode::Disabled),
14873            words_min_length: Some(0),
14874            lsp_insert_mode: Some(LspInsertMode::Insert),
14875            ..Default::default()
14876        });
14877    });
14878
14879    let mut cx = EditorLspTestContext::new_rust(
14880        lsp::ServerCapabilities {
14881            completion_provider: Some(lsp::CompletionOptions {
14882                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14883                ..lsp::CompletionOptions::default()
14884            }),
14885            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14886            ..lsp::ServerCapabilities::default()
14887        },
14888        cx,
14889    )
14890    .await;
14891
14892    let _completion_requests_handler =
14893        cx.lsp
14894            .server
14895            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14896                panic!("LSP completions should not be queried when dealing with word completions")
14897            });
14898
14899    cx.set_state(indoc! {"ˇ
14900        first
14901        last
14902        second
14903    "});
14904    cx.update_editor(|editor, window, cx| {
14905        editor.show_word_completions(&ShowWordCompletions, window, cx);
14906    });
14907    cx.executor().run_until_parked();
14908    cx.condition(|editor, _| editor.context_menu_visible())
14909        .await;
14910    cx.update_editor(|editor, _, _| {
14911        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14912        {
14913            assert_eq!(
14914                completion_menu_entries(menu),
14915                &["first", "last", "second"],
14916                "`ShowWordCompletions` action should show word completions"
14917            );
14918        } else {
14919            panic!("expected completion menu to be open");
14920        }
14921    });
14922
14923    cx.simulate_keystroke("l");
14924    cx.executor().run_until_parked();
14925    cx.condition(|editor, _| editor.context_menu_visible())
14926        .await;
14927    cx.update_editor(|editor, _, _| {
14928        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14929        {
14930            assert_eq!(
14931                completion_menu_entries(menu),
14932                &["last"],
14933                "After showing word completions, further editing should filter them and not query the LSP"
14934            );
14935        } else {
14936            panic!("expected completion menu to be open");
14937        }
14938    });
14939}
14940
14941#[gpui::test]
14942async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
14943    init_test(cx, |language_settings| {
14944        language_settings.defaults.completions = Some(CompletionSettingsContent {
14945            words_min_length: Some(0),
14946            lsp: Some(false),
14947            lsp_insert_mode: Some(LspInsertMode::Insert),
14948            ..Default::default()
14949        });
14950    });
14951
14952    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14953
14954    cx.set_state(indoc! {"ˇ
14955        0_usize
14956        let
14957        33
14958        4.5f32
14959    "});
14960    cx.update_editor(|editor, window, cx| {
14961        editor.show_completions(&ShowCompletions, window, cx);
14962    });
14963    cx.executor().run_until_parked();
14964    cx.condition(|editor, _| editor.context_menu_visible())
14965        .await;
14966    cx.update_editor(|editor, window, cx| {
14967        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14968        {
14969            assert_eq!(
14970                completion_menu_entries(menu),
14971                &["let"],
14972                "With no digits in the completion query, no digits should be in the word completions"
14973            );
14974        } else {
14975            panic!("expected completion menu to be open");
14976        }
14977        editor.cancel(&Cancel, window, cx);
14978    });
14979
14980    cx.set_state(indoc! {"14981        0_usize
14982        let
14983        3
14984        33.35f32
14985    "});
14986    cx.update_editor(|editor, window, cx| {
14987        editor.show_completions(&ShowCompletions, window, cx);
14988    });
14989    cx.executor().run_until_parked();
14990    cx.condition(|editor, _| editor.context_menu_visible())
14991        .await;
14992    cx.update_editor(|editor, _, _| {
14993        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14994        {
14995            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
14996                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
14997        } else {
14998            panic!("expected completion menu to be open");
14999        }
15000    });
15001}
15002
15003#[gpui::test]
15004async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
15005    init_test(cx, |language_settings| {
15006        language_settings.defaults.completions = Some(CompletionSettingsContent {
15007            words: Some(WordsCompletionMode::Enabled),
15008            words_min_length: Some(3),
15009            lsp_insert_mode: Some(LspInsertMode::Insert),
15010            ..Default::default()
15011        });
15012    });
15013
15014    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15015    cx.set_state(indoc! {"ˇ
15016        wow
15017        wowen
15018        wowser
15019    "});
15020    cx.simulate_keystroke("w");
15021    cx.executor().run_until_parked();
15022    cx.update_editor(|editor, _, _| {
15023        if editor.context_menu.borrow_mut().is_some() {
15024            panic!(
15025                "expected completion menu to be hidden, as words completion threshold is not met"
15026            );
15027        }
15028    });
15029
15030    cx.update_editor(|editor, window, cx| {
15031        editor.show_word_completions(&ShowWordCompletions, window, cx);
15032    });
15033    cx.executor().run_until_parked();
15034    cx.update_editor(|editor, window, cx| {
15035        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15036        {
15037            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");
15038        } else {
15039            panic!("expected completion menu to be open after the word completions are called with an action");
15040        }
15041
15042        editor.cancel(&Cancel, window, cx);
15043    });
15044    cx.update_editor(|editor, _, _| {
15045        if editor.context_menu.borrow_mut().is_some() {
15046            panic!("expected completion menu to be hidden after canceling");
15047        }
15048    });
15049
15050    cx.simulate_keystroke("o");
15051    cx.executor().run_until_parked();
15052    cx.update_editor(|editor, _, _| {
15053        if editor.context_menu.borrow_mut().is_some() {
15054            panic!(
15055                "expected completion menu to be hidden, as words completion threshold is not met still"
15056            );
15057        }
15058    });
15059
15060    cx.simulate_keystroke("w");
15061    cx.executor().run_until_parked();
15062    cx.update_editor(|editor, _, _| {
15063        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15064        {
15065            assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
15066        } else {
15067            panic!("expected completion menu to be open after the word completions threshold is met");
15068        }
15069    });
15070}
15071
15072#[gpui::test]
15073async fn test_word_completions_disabled(cx: &mut TestAppContext) {
15074    init_test(cx, |language_settings| {
15075        language_settings.defaults.completions = Some(CompletionSettingsContent {
15076            words: Some(WordsCompletionMode::Enabled),
15077            words_min_length: Some(0),
15078            lsp_insert_mode: Some(LspInsertMode::Insert),
15079            ..Default::default()
15080        });
15081    });
15082
15083    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15084    cx.update_editor(|editor, _, _| {
15085        editor.disable_word_completions();
15086    });
15087    cx.set_state(indoc! {"ˇ
15088        wow
15089        wowen
15090        wowser
15091    "});
15092    cx.simulate_keystroke("w");
15093    cx.executor().run_until_parked();
15094    cx.update_editor(|editor, _, _| {
15095        if editor.context_menu.borrow_mut().is_some() {
15096            panic!(
15097                "expected completion menu to be hidden, as words completion are disabled for this editor"
15098            );
15099        }
15100    });
15101
15102    cx.update_editor(|editor, window, cx| {
15103        editor.show_word_completions(&ShowWordCompletions, window, cx);
15104    });
15105    cx.executor().run_until_parked();
15106    cx.update_editor(|editor, _, _| {
15107        if editor.context_menu.borrow_mut().is_some() {
15108            panic!(
15109                "expected completion menu to be hidden even if called for explicitly, as words completion are disabled for this editor"
15110            );
15111        }
15112    });
15113}
15114
15115fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
15116    let position = || lsp::Position {
15117        line: params.text_document_position.position.line,
15118        character: params.text_document_position.position.character,
15119    };
15120    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15121        range: lsp::Range {
15122            start: position(),
15123            end: position(),
15124        },
15125        new_text: text.to_string(),
15126    }))
15127}
15128
15129#[gpui::test]
15130async fn test_multiline_completion(cx: &mut TestAppContext) {
15131    init_test(cx, |_| {});
15132
15133    let fs = FakeFs::new(cx.executor());
15134    fs.insert_tree(
15135        path!("/a"),
15136        json!({
15137            "main.ts": "a",
15138        }),
15139    )
15140    .await;
15141
15142    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15143    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15144    let typescript_language = Arc::new(Language::new(
15145        LanguageConfig {
15146            name: "TypeScript".into(),
15147            matcher: LanguageMatcher {
15148                path_suffixes: vec!["ts".to_string()],
15149                ..LanguageMatcher::default()
15150            },
15151            line_comments: vec!["// ".into()],
15152            ..LanguageConfig::default()
15153        },
15154        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
15155    ));
15156    language_registry.add(typescript_language.clone());
15157    let mut fake_servers = language_registry.register_fake_lsp(
15158        "TypeScript",
15159        FakeLspAdapter {
15160            capabilities: lsp::ServerCapabilities {
15161                completion_provider: Some(lsp::CompletionOptions {
15162                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15163                    ..lsp::CompletionOptions::default()
15164                }),
15165                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15166                ..lsp::ServerCapabilities::default()
15167            },
15168            // Emulate vtsls label generation
15169            label_for_completion: Some(Box::new(|item, _| {
15170                let text = if let Some(description) = item
15171                    .label_details
15172                    .as_ref()
15173                    .and_then(|label_details| label_details.description.as_ref())
15174                {
15175                    format!("{} {}", item.label, description)
15176                } else if let Some(detail) = &item.detail {
15177                    format!("{} {}", item.label, detail)
15178                } else {
15179                    item.label.clone()
15180                };
15181                Some(language::CodeLabel::plain(text, None))
15182            })),
15183            ..FakeLspAdapter::default()
15184        },
15185    );
15186    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15187    let cx = &mut VisualTestContext::from_window(*workspace, cx);
15188    let worktree_id = workspace
15189        .update(cx, |workspace, _window, cx| {
15190            workspace.project().update(cx, |project, cx| {
15191                project.worktrees(cx).next().unwrap().read(cx).id()
15192            })
15193        })
15194        .unwrap();
15195    let _buffer = project
15196        .update(cx, |project, cx| {
15197            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
15198        })
15199        .await
15200        .unwrap();
15201    let editor = workspace
15202        .update(cx, |workspace, window, cx| {
15203            workspace.open_path((worktree_id, rel_path("main.ts")), None, true, window, cx)
15204        })
15205        .unwrap()
15206        .await
15207        .unwrap()
15208        .downcast::<Editor>()
15209        .unwrap();
15210    let fake_server = fake_servers.next().await.unwrap();
15211
15212    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
15213    let multiline_label_2 = "a\nb\nc\n";
15214    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
15215    let multiline_description = "d\ne\nf\n";
15216    let multiline_detail_2 = "g\nh\ni\n";
15217
15218    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
15219        move |params, _| async move {
15220            Ok(Some(lsp::CompletionResponse::Array(vec![
15221                lsp::CompletionItem {
15222                    label: multiline_label.to_string(),
15223                    text_edit: gen_text_edit(&params, "new_text_1"),
15224                    ..lsp::CompletionItem::default()
15225                },
15226                lsp::CompletionItem {
15227                    label: "single line label 1".to_string(),
15228                    detail: Some(multiline_detail.to_string()),
15229                    text_edit: gen_text_edit(&params, "new_text_2"),
15230                    ..lsp::CompletionItem::default()
15231                },
15232                lsp::CompletionItem {
15233                    label: "single line label 2".to_string(),
15234                    label_details: Some(lsp::CompletionItemLabelDetails {
15235                        description: Some(multiline_description.to_string()),
15236                        detail: None,
15237                    }),
15238                    text_edit: gen_text_edit(&params, "new_text_2"),
15239                    ..lsp::CompletionItem::default()
15240                },
15241                lsp::CompletionItem {
15242                    label: multiline_label_2.to_string(),
15243                    detail: Some(multiline_detail_2.to_string()),
15244                    text_edit: gen_text_edit(&params, "new_text_3"),
15245                    ..lsp::CompletionItem::default()
15246                },
15247                lsp::CompletionItem {
15248                    label: "Label with many     spaces and \t but without newlines".to_string(),
15249                    detail: Some(
15250                        "Details with many     spaces and \t but without newlines".to_string(),
15251                    ),
15252                    text_edit: gen_text_edit(&params, "new_text_4"),
15253                    ..lsp::CompletionItem::default()
15254                },
15255            ])))
15256        },
15257    );
15258
15259    editor.update_in(cx, |editor, window, cx| {
15260        cx.focus_self(window);
15261        editor.move_to_end(&MoveToEnd, window, cx);
15262        editor.handle_input(".", window, cx);
15263    });
15264    cx.run_until_parked();
15265    completion_handle.next().await.unwrap();
15266
15267    editor.update(cx, |editor, _| {
15268        assert!(editor.context_menu_visible());
15269        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15270        {
15271            let completion_labels = menu
15272                .completions
15273                .borrow()
15274                .iter()
15275                .map(|c| c.label.text.clone())
15276                .collect::<Vec<_>>();
15277            assert_eq!(
15278                completion_labels,
15279                &[
15280                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
15281                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
15282                    "single line label 2 d e f ",
15283                    "a b c g h i ",
15284                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
15285                ],
15286                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
15287            );
15288
15289            for completion in menu
15290                .completions
15291                .borrow()
15292                .iter() {
15293                    assert_eq!(
15294                        completion.label.filter_range,
15295                        0..completion.label.text.len(),
15296                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
15297                    );
15298                }
15299        } else {
15300            panic!("expected completion menu to be open");
15301        }
15302    });
15303}
15304
15305#[gpui::test]
15306async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
15307    init_test(cx, |_| {});
15308    let mut cx = EditorLspTestContext::new_rust(
15309        lsp::ServerCapabilities {
15310            completion_provider: Some(lsp::CompletionOptions {
15311                trigger_characters: Some(vec![".".to_string()]),
15312                ..Default::default()
15313            }),
15314            ..Default::default()
15315        },
15316        cx,
15317    )
15318    .await;
15319    cx.lsp
15320        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15321            Ok(Some(lsp::CompletionResponse::Array(vec![
15322                lsp::CompletionItem {
15323                    label: "first".into(),
15324                    ..Default::default()
15325                },
15326                lsp::CompletionItem {
15327                    label: "last".into(),
15328                    ..Default::default()
15329                },
15330            ])))
15331        });
15332    cx.set_state("variableˇ");
15333    cx.simulate_keystroke(".");
15334    cx.executor().run_until_parked();
15335
15336    cx.update_editor(|editor, _, _| {
15337        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15338        {
15339            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
15340        } else {
15341            panic!("expected completion menu to be open");
15342        }
15343    });
15344
15345    cx.update_editor(|editor, window, cx| {
15346        editor.move_page_down(&MovePageDown::default(), window, cx);
15347        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15348        {
15349            assert!(
15350                menu.selected_item == 1,
15351                "expected PageDown to select the last item from the context menu"
15352            );
15353        } else {
15354            panic!("expected completion menu to stay open after PageDown");
15355        }
15356    });
15357
15358    cx.update_editor(|editor, window, cx| {
15359        editor.move_page_up(&MovePageUp::default(), window, cx);
15360        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15361        {
15362            assert!(
15363                menu.selected_item == 0,
15364                "expected PageUp to select the first item from the context menu"
15365            );
15366        } else {
15367            panic!("expected completion menu to stay open after PageUp");
15368        }
15369    });
15370}
15371
15372#[gpui::test]
15373async fn test_as_is_completions(cx: &mut TestAppContext) {
15374    init_test(cx, |_| {});
15375    let mut cx = EditorLspTestContext::new_rust(
15376        lsp::ServerCapabilities {
15377            completion_provider: Some(lsp::CompletionOptions {
15378                ..Default::default()
15379            }),
15380            ..Default::default()
15381        },
15382        cx,
15383    )
15384    .await;
15385    cx.lsp
15386        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15387            Ok(Some(lsp::CompletionResponse::Array(vec![
15388                lsp::CompletionItem {
15389                    label: "unsafe".into(),
15390                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15391                        range: lsp::Range {
15392                            start: lsp::Position {
15393                                line: 1,
15394                                character: 2,
15395                            },
15396                            end: lsp::Position {
15397                                line: 1,
15398                                character: 3,
15399                            },
15400                        },
15401                        new_text: "unsafe".to_string(),
15402                    })),
15403                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
15404                    ..Default::default()
15405                },
15406            ])))
15407        });
15408    cx.set_state("fn a() {}\n");
15409    cx.executor().run_until_parked();
15410    cx.update_editor(|editor, window, cx| {
15411        editor.trigger_completion_on_input("n", true, window, cx)
15412    });
15413    cx.executor().run_until_parked();
15414
15415    cx.update_editor(|editor, window, cx| {
15416        editor.confirm_completion(&Default::default(), window, cx)
15417    });
15418    cx.executor().run_until_parked();
15419    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
15420}
15421
15422#[gpui::test]
15423async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
15424    init_test(cx, |_| {});
15425    let language =
15426        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
15427    let mut cx = EditorLspTestContext::new(
15428        language,
15429        lsp::ServerCapabilities {
15430            completion_provider: Some(lsp::CompletionOptions {
15431                ..lsp::CompletionOptions::default()
15432            }),
15433            ..lsp::ServerCapabilities::default()
15434        },
15435        cx,
15436    )
15437    .await;
15438
15439    cx.set_state(
15440        "#ifndef BAR_H
15441#define BAR_H
15442
15443#include <stdbool.h>
15444
15445int fn_branch(bool do_branch1, bool do_branch2);
15446
15447#endif // BAR_H
15448ˇ",
15449    );
15450    cx.executor().run_until_parked();
15451    cx.update_editor(|editor, window, cx| {
15452        editor.handle_input("#", window, cx);
15453    });
15454    cx.executor().run_until_parked();
15455    cx.update_editor(|editor, window, cx| {
15456        editor.handle_input("i", window, cx);
15457    });
15458    cx.executor().run_until_parked();
15459    cx.update_editor(|editor, window, cx| {
15460        editor.handle_input("n", window, cx);
15461    });
15462    cx.executor().run_until_parked();
15463    cx.assert_editor_state(
15464        "#ifndef BAR_H
15465#define BAR_H
15466
15467#include <stdbool.h>
15468
15469int fn_branch(bool do_branch1, bool do_branch2);
15470
15471#endif // BAR_H
15472#inˇ",
15473    );
15474
15475    cx.lsp
15476        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15477            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15478                is_incomplete: false,
15479                item_defaults: None,
15480                items: vec![lsp::CompletionItem {
15481                    kind: Some(lsp::CompletionItemKind::SNIPPET),
15482                    label_details: Some(lsp::CompletionItemLabelDetails {
15483                        detail: Some("header".to_string()),
15484                        description: None,
15485                    }),
15486                    label: " include".to_string(),
15487                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15488                        range: lsp::Range {
15489                            start: lsp::Position {
15490                                line: 8,
15491                                character: 1,
15492                            },
15493                            end: lsp::Position {
15494                                line: 8,
15495                                character: 1,
15496                            },
15497                        },
15498                        new_text: "include \"$0\"".to_string(),
15499                    })),
15500                    sort_text: Some("40b67681include".to_string()),
15501                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15502                    filter_text: Some("include".to_string()),
15503                    insert_text: Some("include \"$0\"".to_string()),
15504                    ..lsp::CompletionItem::default()
15505                }],
15506            })))
15507        });
15508    cx.update_editor(|editor, window, cx| {
15509        editor.show_completions(&ShowCompletions, window, cx);
15510    });
15511    cx.executor().run_until_parked();
15512    cx.update_editor(|editor, window, cx| {
15513        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15514    });
15515    cx.executor().run_until_parked();
15516    cx.assert_editor_state(
15517        "#ifndef BAR_H
15518#define BAR_H
15519
15520#include <stdbool.h>
15521
15522int fn_branch(bool do_branch1, bool do_branch2);
15523
15524#endif // BAR_H
15525#include \"ˇ\"",
15526    );
15527
15528    cx.lsp
15529        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15530            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15531                is_incomplete: true,
15532                item_defaults: None,
15533                items: vec![lsp::CompletionItem {
15534                    kind: Some(lsp::CompletionItemKind::FILE),
15535                    label: "AGL/".to_string(),
15536                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15537                        range: lsp::Range {
15538                            start: lsp::Position {
15539                                line: 8,
15540                                character: 10,
15541                            },
15542                            end: lsp::Position {
15543                                line: 8,
15544                                character: 11,
15545                            },
15546                        },
15547                        new_text: "AGL/".to_string(),
15548                    })),
15549                    sort_text: Some("40b67681AGL/".to_string()),
15550                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
15551                    filter_text: Some("AGL/".to_string()),
15552                    insert_text: Some("AGL/".to_string()),
15553                    ..lsp::CompletionItem::default()
15554                }],
15555            })))
15556        });
15557    cx.update_editor(|editor, window, cx| {
15558        editor.show_completions(&ShowCompletions, window, cx);
15559    });
15560    cx.executor().run_until_parked();
15561    cx.update_editor(|editor, window, cx| {
15562        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15563    });
15564    cx.executor().run_until_parked();
15565    cx.assert_editor_state(
15566        r##"#ifndef BAR_H
15567#define BAR_H
15568
15569#include <stdbool.h>
15570
15571int fn_branch(bool do_branch1, bool do_branch2);
15572
15573#endif // BAR_H
15574#include "AGL/ˇ"##,
15575    );
15576
15577    cx.update_editor(|editor, window, cx| {
15578        editor.handle_input("\"", window, cx);
15579    });
15580    cx.executor().run_until_parked();
15581    cx.assert_editor_state(
15582        r##"#ifndef BAR_H
15583#define BAR_H
15584
15585#include <stdbool.h>
15586
15587int fn_branch(bool do_branch1, bool do_branch2);
15588
15589#endif // BAR_H
15590#include "AGL/"ˇ"##,
15591    );
15592}
15593
15594#[gpui::test]
15595async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
15596    init_test(cx, |_| {});
15597
15598    let mut cx = EditorLspTestContext::new_rust(
15599        lsp::ServerCapabilities {
15600            completion_provider: Some(lsp::CompletionOptions {
15601                trigger_characters: Some(vec![".".to_string()]),
15602                resolve_provider: Some(true),
15603                ..Default::default()
15604            }),
15605            ..Default::default()
15606        },
15607        cx,
15608    )
15609    .await;
15610
15611    cx.set_state("fn main() { let a = 2ˇ; }");
15612    cx.simulate_keystroke(".");
15613    let completion_item = lsp::CompletionItem {
15614        label: "Some".into(),
15615        kind: Some(lsp::CompletionItemKind::SNIPPET),
15616        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15617        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15618            kind: lsp::MarkupKind::Markdown,
15619            value: "```rust\nSome(2)\n```".to_string(),
15620        })),
15621        deprecated: Some(false),
15622        sort_text: Some("Some".to_string()),
15623        filter_text: Some("Some".to_string()),
15624        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15625        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15626            range: lsp::Range {
15627                start: lsp::Position {
15628                    line: 0,
15629                    character: 22,
15630                },
15631                end: lsp::Position {
15632                    line: 0,
15633                    character: 22,
15634                },
15635            },
15636            new_text: "Some(2)".to_string(),
15637        })),
15638        additional_text_edits: Some(vec![lsp::TextEdit {
15639            range: lsp::Range {
15640                start: lsp::Position {
15641                    line: 0,
15642                    character: 20,
15643                },
15644                end: lsp::Position {
15645                    line: 0,
15646                    character: 22,
15647                },
15648            },
15649            new_text: "".to_string(),
15650        }]),
15651        ..Default::default()
15652    };
15653
15654    let closure_completion_item = completion_item.clone();
15655    let counter = Arc::new(AtomicUsize::new(0));
15656    let counter_clone = counter.clone();
15657    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15658        let task_completion_item = closure_completion_item.clone();
15659        counter_clone.fetch_add(1, atomic::Ordering::Release);
15660        async move {
15661            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15662                is_incomplete: true,
15663                item_defaults: None,
15664                items: vec![task_completion_item],
15665            })))
15666        }
15667    });
15668
15669    cx.condition(|editor, _| editor.context_menu_visible())
15670        .await;
15671    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
15672    assert!(request.next().await.is_some());
15673    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15674
15675    cx.simulate_keystrokes("S o m");
15676    cx.condition(|editor, _| editor.context_menu_visible())
15677        .await;
15678    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
15679    assert!(request.next().await.is_some());
15680    assert!(request.next().await.is_some());
15681    assert!(request.next().await.is_some());
15682    request.close();
15683    assert!(request.next().await.is_none());
15684    assert_eq!(
15685        counter.load(atomic::Ordering::Acquire),
15686        4,
15687        "With the completions menu open, only one LSP request should happen per input"
15688    );
15689}
15690
15691#[gpui::test]
15692async fn test_toggle_comment(cx: &mut TestAppContext) {
15693    init_test(cx, |_| {});
15694    let mut cx = EditorTestContext::new(cx).await;
15695    let language = Arc::new(Language::new(
15696        LanguageConfig {
15697            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15698            ..Default::default()
15699        },
15700        Some(tree_sitter_rust::LANGUAGE.into()),
15701    ));
15702    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15703
15704    // If multiple selections intersect a line, the line is only toggled once.
15705    cx.set_state(indoc! {"
15706        fn a() {
15707            «//b();
15708            ˇ»// «c();
15709            //ˇ»  d();
15710        }
15711    "});
15712
15713    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15714
15715    cx.assert_editor_state(indoc! {"
15716        fn a() {
15717            «b();
15718            c();
15719            ˇ» d();
15720        }
15721    "});
15722
15723    // The comment prefix is inserted at the same column for every line in a
15724    // selection.
15725    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15726
15727    cx.assert_editor_state(indoc! {"
15728        fn a() {
15729            // «b();
15730            // c();
15731            ˇ»//  d();
15732        }
15733    "});
15734
15735    // If a selection ends at the beginning of a line, that line is not toggled.
15736    cx.set_selections_state(indoc! {"
15737        fn a() {
15738            // b();
15739            «// c();
15740        ˇ»    //  d();
15741        }
15742    "});
15743
15744    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15745
15746    cx.assert_editor_state(indoc! {"
15747        fn a() {
15748            // b();
15749            «c();
15750        ˇ»    //  d();
15751        }
15752    "});
15753
15754    // If a selection span a single line and is empty, the line is toggled.
15755    cx.set_state(indoc! {"
15756        fn a() {
15757            a();
15758            b();
15759        ˇ
15760        }
15761    "});
15762
15763    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15764
15765    cx.assert_editor_state(indoc! {"
15766        fn a() {
15767            a();
15768            b();
15769        //•ˇ
15770        }
15771    "});
15772
15773    // If a selection span multiple lines, empty lines are not toggled.
15774    cx.set_state(indoc! {"
15775        fn a() {
15776            «a();
15777
15778            c();ˇ»
15779        }
15780    "});
15781
15782    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15783
15784    cx.assert_editor_state(indoc! {"
15785        fn a() {
15786            // «a();
15787
15788            // c();ˇ»
15789        }
15790    "});
15791
15792    // If a selection includes multiple comment prefixes, all lines are uncommented.
15793    cx.set_state(indoc! {"
15794        fn a() {
15795            «// a();
15796            /// b();
15797            //! c();ˇ»
15798        }
15799    "});
15800
15801    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15802
15803    cx.assert_editor_state(indoc! {"
15804        fn a() {
15805            «a();
15806            b();
15807            c();ˇ»
15808        }
15809    "});
15810}
15811
15812#[gpui::test]
15813async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
15814    init_test(cx, |_| {});
15815    let mut cx = EditorTestContext::new(cx).await;
15816    let language = Arc::new(Language::new(
15817        LanguageConfig {
15818            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15819            ..Default::default()
15820        },
15821        Some(tree_sitter_rust::LANGUAGE.into()),
15822    ));
15823    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15824
15825    let toggle_comments = &ToggleComments {
15826        advance_downwards: false,
15827        ignore_indent: true,
15828    };
15829
15830    // If multiple selections intersect a line, the line is only toggled once.
15831    cx.set_state(indoc! {"
15832        fn a() {
15833        //    «b();
15834        //    c();
15835        //    ˇ» d();
15836        }
15837    "});
15838
15839    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15840
15841    cx.assert_editor_state(indoc! {"
15842        fn a() {
15843            «b();
15844            c();
15845            ˇ» d();
15846        }
15847    "});
15848
15849    // The comment prefix is inserted at the beginning of each line
15850    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15851
15852    cx.assert_editor_state(indoc! {"
15853        fn a() {
15854        //    «b();
15855        //    c();
15856        //    ˇ» d();
15857        }
15858    "});
15859
15860    // If a selection ends at the beginning of a line, that line is not toggled.
15861    cx.set_selections_state(indoc! {"
15862        fn a() {
15863        //    b();
15864        //    «c();
15865        ˇ»//     d();
15866        }
15867    "});
15868
15869    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15870
15871    cx.assert_editor_state(indoc! {"
15872        fn a() {
15873        //    b();
15874            «c();
15875        ˇ»//     d();
15876        }
15877    "});
15878
15879    // If a selection span a single line and is empty, the line is toggled.
15880    cx.set_state(indoc! {"
15881        fn a() {
15882            a();
15883            b();
15884        ˇ
15885        }
15886    "});
15887
15888    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15889
15890    cx.assert_editor_state(indoc! {"
15891        fn a() {
15892            a();
15893            b();
15894        //ˇ
15895        }
15896    "});
15897
15898    // If a selection span multiple lines, empty lines are not toggled.
15899    cx.set_state(indoc! {"
15900        fn a() {
15901            «a();
15902
15903            c();ˇ»
15904        }
15905    "});
15906
15907    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15908
15909    cx.assert_editor_state(indoc! {"
15910        fn a() {
15911        //    «a();
15912
15913        //    c();ˇ»
15914        }
15915    "});
15916
15917    // If a selection includes multiple comment prefixes, all lines are uncommented.
15918    cx.set_state(indoc! {"
15919        fn a() {
15920        //    «a();
15921        ///    b();
15922        //!    c();ˇ»
15923        }
15924    "});
15925
15926    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15927
15928    cx.assert_editor_state(indoc! {"
15929        fn a() {
15930            «a();
15931            b();
15932            c();ˇ»
15933        }
15934    "});
15935}
15936
15937#[gpui::test]
15938async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
15939    init_test(cx, |_| {});
15940
15941    let language = Arc::new(Language::new(
15942        LanguageConfig {
15943            line_comments: vec!["// ".into()],
15944            ..Default::default()
15945        },
15946        Some(tree_sitter_rust::LANGUAGE.into()),
15947    ));
15948
15949    let mut cx = EditorTestContext::new(cx).await;
15950
15951    cx.language_registry().add(language.clone());
15952    cx.update_buffer(|buffer, cx| {
15953        buffer.set_language(Some(language), cx);
15954    });
15955
15956    let toggle_comments = &ToggleComments {
15957        advance_downwards: true,
15958        ignore_indent: false,
15959    };
15960
15961    // Single cursor on one line -> advance
15962    // Cursor moves horizontally 3 characters as well on non-blank line
15963    cx.set_state(indoc!(
15964        "fn a() {
15965             ˇdog();
15966             cat();
15967        }"
15968    ));
15969    cx.update_editor(|editor, window, cx| {
15970        editor.toggle_comments(toggle_comments, window, cx);
15971    });
15972    cx.assert_editor_state(indoc!(
15973        "fn a() {
15974             // dog();
15975             catˇ();
15976        }"
15977    ));
15978
15979    // Single selection on one line -> don't advance
15980    cx.set_state(indoc!(
15981        "fn a() {
15982             «dog()ˇ»;
15983             cat();
15984        }"
15985    ));
15986    cx.update_editor(|editor, window, cx| {
15987        editor.toggle_comments(toggle_comments, window, cx);
15988    });
15989    cx.assert_editor_state(indoc!(
15990        "fn a() {
15991             // «dog()ˇ»;
15992             cat();
15993        }"
15994    ));
15995
15996    // Multiple cursors on one line -> advance
15997    cx.set_state(indoc!(
15998        "fn a() {
15999             ˇdˇog();
16000             cat();
16001        }"
16002    ));
16003    cx.update_editor(|editor, window, cx| {
16004        editor.toggle_comments(toggle_comments, window, cx);
16005    });
16006    cx.assert_editor_state(indoc!(
16007        "fn a() {
16008             // dog();
16009             catˇ(ˇ);
16010        }"
16011    ));
16012
16013    // Multiple cursors on one line, with selection -> don't advance
16014    cx.set_state(indoc!(
16015        "fn a() {
16016             ˇdˇog«()ˇ»;
16017             cat();
16018        }"
16019    ));
16020    cx.update_editor(|editor, window, cx| {
16021        editor.toggle_comments(toggle_comments, window, cx);
16022    });
16023    cx.assert_editor_state(indoc!(
16024        "fn a() {
16025             // ˇdˇog«()ˇ»;
16026             cat();
16027        }"
16028    ));
16029
16030    // Single cursor on one line -> advance
16031    // Cursor moves to column 0 on blank line
16032    cx.set_state(indoc!(
16033        "fn a() {
16034             ˇdog();
16035
16036             cat();
16037        }"
16038    ));
16039    cx.update_editor(|editor, window, cx| {
16040        editor.toggle_comments(toggle_comments, window, cx);
16041    });
16042    cx.assert_editor_state(indoc!(
16043        "fn a() {
16044             // dog();
16045        ˇ
16046             cat();
16047        }"
16048    ));
16049
16050    // Single cursor on one line -> advance
16051    // Cursor starts and ends at column 0
16052    cx.set_state(indoc!(
16053        "fn a() {
16054         ˇ    dog();
16055             cat();
16056        }"
16057    ));
16058    cx.update_editor(|editor, window, cx| {
16059        editor.toggle_comments(toggle_comments, window, cx);
16060    });
16061    cx.assert_editor_state(indoc!(
16062        "fn a() {
16063             // dog();
16064         ˇ    cat();
16065        }"
16066    ));
16067}
16068
16069#[gpui::test]
16070async fn test_toggle_block_comment(cx: &mut TestAppContext) {
16071    init_test(cx, |_| {});
16072
16073    let mut cx = EditorTestContext::new(cx).await;
16074
16075    let html_language = Arc::new(
16076        Language::new(
16077            LanguageConfig {
16078                name: "HTML".into(),
16079                block_comment: Some(BlockCommentConfig {
16080                    start: "<!-- ".into(),
16081                    prefix: "".into(),
16082                    end: " -->".into(),
16083                    tab_size: 0,
16084                }),
16085                ..Default::default()
16086            },
16087            Some(tree_sitter_html::LANGUAGE.into()),
16088        )
16089        .with_injection_query(
16090            r#"
16091            (script_element
16092                (raw_text) @injection.content
16093                (#set! injection.language "javascript"))
16094            "#,
16095        )
16096        .unwrap(),
16097    );
16098
16099    let javascript_language = Arc::new(Language::new(
16100        LanguageConfig {
16101            name: "JavaScript".into(),
16102            line_comments: vec!["// ".into()],
16103            ..Default::default()
16104        },
16105        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
16106    ));
16107
16108    cx.language_registry().add(html_language.clone());
16109    cx.language_registry().add(javascript_language);
16110    cx.update_buffer(|buffer, cx| {
16111        buffer.set_language(Some(html_language), cx);
16112    });
16113
16114    // Toggle comments for empty selections
16115    cx.set_state(
16116        &r#"
16117            <p>A</p>ˇ
16118            <p>B</p>ˇ
16119            <p>C</p>ˇ
16120        "#
16121        .unindent(),
16122    );
16123    cx.update_editor(|editor, window, cx| {
16124        editor.toggle_comments(&ToggleComments::default(), window, cx)
16125    });
16126    cx.assert_editor_state(
16127        &r#"
16128            <!-- <p>A</p>ˇ -->
16129            <!-- <p>B</p>ˇ -->
16130            <!-- <p>C</p>ˇ -->
16131        "#
16132        .unindent(),
16133    );
16134    cx.update_editor(|editor, window, cx| {
16135        editor.toggle_comments(&ToggleComments::default(), window, cx)
16136    });
16137    cx.assert_editor_state(
16138        &r#"
16139            <p>A</p>ˇ
16140            <p>B</p>ˇ
16141            <p>C</p>ˇ
16142        "#
16143        .unindent(),
16144    );
16145
16146    // Toggle comments for mixture of empty and non-empty selections, where
16147    // multiple selections occupy a given line.
16148    cx.set_state(
16149        &r#"
16150            <p>A«</p>
16151            <p>ˇ»B</p>ˇ
16152            <p>C«</p>
16153            <p>ˇ»D</p>ˇ
16154        "#
16155        .unindent(),
16156    );
16157
16158    cx.update_editor(|editor, window, cx| {
16159        editor.toggle_comments(&ToggleComments::default(), window, cx)
16160    });
16161    cx.assert_editor_state(
16162        &r#"
16163            <!-- <p>A«</p>
16164            <p>ˇ»B</p>ˇ -->
16165            <!-- <p>C«</p>
16166            <p>ˇ»D</p>ˇ -->
16167        "#
16168        .unindent(),
16169    );
16170    cx.update_editor(|editor, window, cx| {
16171        editor.toggle_comments(&ToggleComments::default(), window, cx)
16172    });
16173    cx.assert_editor_state(
16174        &r#"
16175            <p>A«</p>
16176            <p>ˇ»B</p>ˇ
16177            <p>C«</p>
16178            <p>ˇ»D</p>ˇ
16179        "#
16180        .unindent(),
16181    );
16182
16183    // Toggle comments when different languages are active for different
16184    // selections.
16185    cx.set_state(
16186        &r#"
16187            ˇ<script>
16188                ˇvar x = new Y();
16189            ˇ</script>
16190        "#
16191        .unindent(),
16192    );
16193    cx.executor().run_until_parked();
16194    cx.update_editor(|editor, window, cx| {
16195        editor.toggle_comments(&ToggleComments::default(), window, cx)
16196    });
16197    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
16198    // Uncommenting and commenting from this position brings in even more wrong artifacts.
16199    cx.assert_editor_state(
16200        &r#"
16201            <!-- ˇ<script> -->
16202                // ˇvar x = new Y();
16203            <!-- ˇ</script> -->
16204        "#
16205        .unindent(),
16206    );
16207}
16208
16209#[gpui::test]
16210fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
16211    init_test(cx, |_| {});
16212
16213    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16214    let multibuffer = cx.new(|cx| {
16215        let mut multibuffer = MultiBuffer::new(ReadWrite);
16216        multibuffer.push_excerpts(
16217            buffer.clone(),
16218            [
16219                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
16220                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
16221            ],
16222            cx,
16223        );
16224        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
16225        multibuffer
16226    });
16227
16228    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
16229    editor.update_in(cx, |editor, window, cx| {
16230        assert_eq!(editor.text(cx), "aaaa\nbbbb");
16231        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16232            s.select_ranges([
16233                Point::new(0, 0)..Point::new(0, 0),
16234                Point::new(1, 0)..Point::new(1, 0),
16235            ])
16236        });
16237
16238        editor.handle_input("X", window, cx);
16239        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
16240        assert_eq!(
16241            editor.selections.ranges(&editor.display_snapshot(cx)),
16242            [
16243                Point::new(0, 1)..Point::new(0, 1),
16244                Point::new(1, 1)..Point::new(1, 1),
16245            ]
16246        );
16247
16248        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
16249        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16250            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
16251        });
16252        editor.backspace(&Default::default(), window, cx);
16253        assert_eq!(editor.text(cx), "Xa\nbbb");
16254        assert_eq!(
16255            editor.selections.ranges(&editor.display_snapshot(cx)),
16256            [Point::new(1, 0)..Point::new(1, 0)]
16257        );
16258
16259        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16260            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
16261        });
16262        editor.backspace(&Default::default(), window, cx);
16263        assert_eq!(editor.text(cx), "X\nbb");
16264        assert_eq!(
16265            editor.selections.ranges(&editor.display_snapshot(cx)),
16266            [Point::new(0, 1)..Point::new(0, 1)]
16267        );
16268    });
16269}
16270
16271#[gpui::test]
16272fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
16273    init_test(cx, |_| {});
16274
16275    let markers = vec![('[', ']').into(), ('(', ')').into()];
16276    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
16277        indoc! {"
16278            [aaaa
16279            (bbbb]
16280            cccc)",
16281        },
16282        markers.clone(),
16283    );
16284    let excerpt_ranges = markers.into_iter().map(|marker| {
16285        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
16286        ExcerptRange::new(context)
16287    });
16288    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
16289    let multibuffer = cx.new(|cx| {
16290        let mut multibuffer = MultiBuffer::new(ReadWrite);
16291        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
16292        multibuffer
16293    });
16294
16295    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
16296    editor.update_in(cx, |editor, window, cx| {
16297        let (expected_text, selection_ranges) = marked_text_ranges(
16298            indoc! {"
16299                aaaa
16300                bˇbbb
16301                bˇbbˇb
16302                cccc"
16303            },
16304            true,
16305        );
16306        assert_eq!(editor.text(cx), expected_text);
16307        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16308            s.select_ranges(selection_ranges)
16309        });
16310
16311        editor.handle_input("X", window, cx);
16312
16313        let (expected_text, expected_selections) = marked_text_ranges(
16314            indoc! {"
16315                aaaa
16316                bXˇbbXb
16317                bXˇbbXˇb
16318                cccc"
16319            },
16320            false,
16321        );
16322        assert_eq!(editor.text(cx), expected_text);
16323        assert_eq!(
16324            editor.selections.ranges(&editor.display_snapshot(cx)),
16325            expected_selections
16326        );
16327
16328        editor.newline(&Newline, window, cx);
16329        let (expected_text, expected_selections) = marked_text_ranges(
16330            indoc! {"
16331                aaaa
16332                bX
16333                ˇbbX
16334                b
16335                bX
16336                ˇbbX
16337                ˇb
16338                cccc"
16339            },
16340            false,
16341        );
16342        assert_eq!(editor.text(cx), expected_text);
16343        assert_eq!(
16344            editor.selections.ranges(&editor.display_snapshot(cx)),
16345            expected_selections
16346        );
16347    });
16348}
16349
16350#[gpui::test]
16351fn test_refresh_selections(cx: &mut TestAppContext) {
16352    init_test(cx, |_| {});
16353
16354    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16355    let mut excerpt1_id = None;
16356    let multibuffer = cx.new(|cx| {
16357        let mut multibuffer = MultiBuffer::new(ReadWrite);
16358        excerpt1_id = multibuffer
16359            .push_excerpts(
16360                buffer.clone(),
16361                [
16362                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
16363                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
16364                ],
16365                cx,
16366            )
16367            .into_iter()
16368            .next();
16369        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
16370        multibuffer
16371    });
16372
16373    let editor = cx.add_window(|window, cx| {
16374        let mut editor = build_editor(multibuffer.clone(), window, cx);
16375        let snapshot = editor.snapshot(window, cx);
16376        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16377            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
16378        });
16379        editor.begin_selection(
16380            Point::new(2, 1).to_display_point(&snapshot),
16381            true,
16382            1,
16383            window,
16384            cx,
16385        );
16386        assert_eq!(
16387            editor.selections.ranges(&editor.display_snapshot(cx)),
16388            [
16389                Point::new(1, 3)..Point::new(1, 3),
16390                Point::new(2, 1)..Point::new(2, 1),
16391            ]
16392        );
16393        editor
16394    });
16395
16396    // Refreshing selections is a no-op when excerpts haven't changed.
16397    _ = editor.update(cx, |editor, window, cx| {
16398        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16399        assert_eq!(
16400            editor.selections.ranges(&editor.display_snapshot(cx)),
16401            [
16402                Point::new(1, 3)..Point::new(1, 3),
16403                Point::new(2, 1)..Point::new(2, 1),
16404            ]
16405        );
16406    });
16407
16408    multibuffer.update(cx, |multibuffer, cx| {
16409        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
16410    });
16411    _ = editor.update(cx, |editor, window, cx| {
16412        // Removing an excerpt causes the first selection to become degenerate.
16413        assert_eq!(
16414            editor.selections.ranges(&editor.display_snapshot(cx)),
16415            [
16416                Point::new(0, 0)..Point::new(0, 0),
16417                Point::new(0, 1)..Point::new(0, 1)
16418            ]
16419        );
16420
16421        // Refreshing selections will relocate the first selection to the original buffer
16422        // location.
16423        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16424        assert_eq!(
16425            editor.selections.ranges(&editor.display_snapshot(cx)),
16426            [
16427                Point::new(0, 1)..Point::new(0, 1),
16428                Point::new(0, 3)..Point::new(0, 3)
16429            ]
16430        );
16431        assert!(editor.selections.pending_anchor().is_some());
16432    });
16433}
16434
16435#[gpui::test]
16436fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
16437    init_test(cx, |_| {});
16438
16439    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16440    let mut excerpt1_id = None;
16441    let multibuffer = cx.new(|cx| {
16442        let mut multibuffer = MultiBuffer::new(ReadWrite);
16443        excerpt1_id = multibuffer
16444            .push_excerpts(
16445                buffer.clone(),
16446                [
16447                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
16448                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
16449                ],
16450                cx,
16451            )
16452            .into_iter()
16453            .next();
16454        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
16455        multibuffer
16456    });
16457
16458    let editor = cx.add_window(|window, cx| {
16459        let mut editor = build_editor(multibuffer.clone(), window, cx);
16460        let snapshot = editor.snapshot(window, cx);
16461        editor.begin_selection(
16462            Point::new(1, 3).to_display_point(&snapshot),
16463            false,
16464            1,
16465            window,
16466            cx,
16467        );
16468        assert_eq!(
16469            editor.selections.ranges(&editor.display_snapshot(cx)),
16470            [Point::new(1, 3)..Point::new(1, 3)]
16471        );
16472        editor
16473    });
16474
16475    multibuffer.update(cx, |multibuffer, cx| {
16476        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
16477    });
16478    _ = editor.update(cx, |editor, window, cx| {
16479        assert_eq!(
16480            editor.selections.ranges(&editor.display_snapshot(cx)),
16481            [Point::new(0, 0)..Point::new(0, 0)]
16482        );
16483
16484        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
16485        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16486        assert_eq!(
16487            editor.selections.ranges(&editor.display_snapshot(cx)),
16488            [Point::new(0, 3)..Point::new(0, 3)]
16489        );
16490        assert!(editor.selections.pending_anchor().is_some());
16491    });
16492}
16493
16494#[gpui::test]
16495async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
16496    init_test(cx, |_| {});
16497
16498    let language = Arc::new(
16499        Language::new(
16500            LanguageConfig {
16501                brackets: BracketPairConfig {
16502                    pairs: vec![
16503                        BracketPair {
16504                            start: "{".to_string(),
16505                            end: "}".to_string(),
16506                            close: true,
16507                            surround: true,
16508                            newline: true,
16509                        },
16510                        BracketPair {
16511                            start: "/* ".to_string(),
16512                            end: " */".to_string(),
16513                            close: true,
16514                            surround: true,
16515                            newline: true,
16516                        },
16517                    ],
16518                    ..Default::default()
16519                },
16520                ..Default::default()
16521            },
16522            Some(tree_sitter_rust::LANGUAGE.into()),
16523        )
16524        .with_indents_query("")
16525        .unwrap(),
16526    );
16527
16528    let text = concat!(
16529        "{   }\n",     //
16530        "  x\n",       //
16531        "  /*   */\n", //
16532        "x\n",         //
16533        "{{} }\n",     //
16534    );
16535
16536    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
16537    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
16538    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
16539    editor
16540        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
16541        .await;
16542
16543    editor.update_in(cx, |editor, window, cx| {
16544        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16545            s.select_display_ranges([
16546                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
16547                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
16548                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
16549            ])
16550        });
16551        editor.newline(&Newline, window, cx);
16552
16553        assert_eq!(
16554            editor.buffer().read(cx).read(cx).text(),
16555            concat!(
16556                "{ \n",    // Suppress rustfmt
16557                "\n",      //
16558                "}\n",     //
16559                "  x\n",   //
16560                "  /* \n", //
16561                "  \n",    //
16562                "  */\n",  //
16563                "x\n",     //
16564                "{{} \n",  //
16565                "}\n",     //
16566            )
16567        );
16568    });
16569}
16570
16571#[gpui::test]
16572fn test_highlighted_ranges(cx: &mut TestAppContext) {
16573    init_test(cx, |_| {});
16574
16575    let editor = cx.add_window(|window, cx| {
16576        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
16577        build_editor(buffer, window, cx)
16578    });
16579
16580    _ = editor.update(cx, |editor, window, cx| {
16581        struct Type1;
16582        struct Type2;
16583
16584        let buffer = editor.buffer.read(cx).snapshot(cx);
16585
16586        let anchor_range =
16587            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
16588
16589        editor.highlight_background::<Type1>(
16590            &[
16591                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
16592                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
16593                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
16594                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
16595            ],
16596            |_| Hsla::red(),
16597            cx,
16598        );
16599        editor.highlight_background::<Type2>(
16600            &[
16601                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
16602                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
16603                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
16604                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
16605            ],
16606            |_| Hsla::green(),
16607            cx,
16608        );
16609
16610        let snapshot = editor.snapshot(window, cx);
16611        let highlighted_ranges = editor.sorted_background_highlights_in_range(
16612            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
16613            &snapshot,
16614            cx.theme(),
16615        );
16616        assert_eq!(
16617            highlighted_ranges,
16618            &[
16619                (
16620                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
16621                    Hsla::green(),
16622                ),
16623                (
16624                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
16625                    Hsla::red(),
16626                ),
16627                (
16628                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
16629                    Hsla::green(),
16630                ),
16631                (
16632                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16633                    Hsla::red(),
16634                ),
16635            ]
16636        );
16637        assert_eq!(
16638            editor.sorted_background_highlights_in_range(
16639                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
16640                &snapshot,
16641                cx.theme(),
16642            ),
16643            &[(
16644                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16645                Hsla::red(),
16646            )]
16647        );
16648    });
16649}
16650
16651#[gpui::test]
16652async fn test_following(cx: &mut TestAppContext) {
16653    init_test(cx, |_| {});
16654
16655    let fs = FakeFs::new(cx.executor());
16656    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16657
16658    let buffer = project.update(cx, |project, cx| {
16659        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, false, cx);
16660        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
16661    });
16662    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
16663    let follower = cx.update(|cx| {
16664        cx.open_window(
16665            WindowOptions {
16666                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
16667                    gpui::Point::new(px(0.), px(0.)),
16668                    gpui::Point::new(px(10.), px(80.)),
16669                ))),
16670                ..Default::default()
16671            },
16672            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
16673        )
16674        .unwrap()
16675    });
16676
16677    let is_still_following = Rc::new(RefCell::new(true));
16678    let follower_edit_event_count = Rc::new(RefCell::new(0));
16679    let pending_update = Rc::new(RefCell::new(None));
16680    let leader_entity = leader.root(cx).unwrap();
16681    let follower_entity = follower.root(cx).unwrap();
16682    _ = follower.update(cx, {
16683        let update = pending_update.clone();
16684        let is_still_following = is_still_following.clone();
16685        let follower_edit_event_count = follower_edit_event_count.clone();
16686        |_, window, cx| {
16687            cx.subscribe_in(
16688                &leader_entity,
16689                window,
16690                move |_, leader, event, window, cx| {
16691                    leader.read(cx).add_event_to_update_proto(
16692                        event,
16693                        &mut update.borrow_mut(),
16694                        window,
16695                        cx,
16696                    );
16697                },
16698            )
16699            .detach();
16700
16701            cx.subscribe_in(
16702                &follower_entity,
16703                window,
16704                move |_, _, event: &EditorEvent, _window, _cx| {
16705                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
16706                        *is_still_following.borrow_mut() = false;
16707                    }
16708
16709                    if let EditorEvent::BufferEdited = event {
16710                        *follower_edit_event_count.borrow_mut() += 1;
16711                    }
16712                },
16713            )
16714            .detach();
16715        }
16716    });
16717
16718    // Update the selections only
16719    _ = leader.update(cx, |leader, window, cx| {
16720        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16721            s.select_ranges([1..1])
16722        });
16723    });
16724    follower
16725        .update(cx, |follower, window, cx| {
16726            follower.apply_update_proto(
16727                &project,
16728                pending_update.borrow_mut().take().unwrap(),
16729                window,
16730                cx,
16731            )
16732        })
16733        .unwrap()
16734        .await
16735        .unwrap();
16736    _ = follower.update(cx, |follower, _, cx| {
16737        assert_eq!(
16738            follower.selections.ranges(&follower.display_snapshot(cx)),
16739            vec![1..1]
16740        );
16741    });
16742    assert!(*is_still_following.borrow());
16743    assert_eq!(*follower_edit_event_count.borrow(), 0);
16744
16745    // Update the scroll position only
16746    _ = leader.update(cx, |leader, window, cx| {
16747        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16748    });
16749    follower
16750        .update(cx, |follower, window, cx| {
16751            follower.apply_update_proto(
16752                &project,
16753                pending_update.borrow_mut().take().unwrap(),
16754                window,
16755                cx,
16756            )
16757        })
16758        .unwrap()
16759        .await
16760        .unwrap();
16761    assert_eq!(
16762        follower
16763            .update(cx, |follower, _, cx| follower.scroll_position(cx))
16764            .unwrap(),
16765        gpui::Point::new(1.5, 3.5)
16766    );
16767    assert!(*is_still_following.borrow());
16768    assert_eq!(*follower_edit_event_count.borrow(), 0);
16769
16770    // Update the selections and scroll position. The follower's scroll position is updated
16771    // via autoscroll, not via the leader's exact scroll position.
16772    _ = leader.update(cx, |leader, window, cx| {
16773        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16774            s.select_ranges([0..0])
16775        });
16776        leader.request_autoscroll(Autoscroll::newest(), cx);
16777        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16778    });
16779    follower
16780        .update(cx, |follower, window, cx| {
16781            follower.apply_update_proto(
16782                &project,
16783                pending_update.borrow_mut().take().unwrap(),
16784                window,
16785                cx,
16786            )
16787        })
16788        .unwrap()
16789        .await
16790        .unwrap();
16791    _ = follower.update(cx, |follower, _, cx| {
16792        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
16793        assert_eq!(
16794            follower.selections.ranges(&follower.display_snapshot(cx)),
16795            vec![0..0]
16796        );
16797    });
16798    assert!(*is_still_following.borrow());
16799
16800    // Creating a pending selection that precedes another selection
16801    _ = leader.update(cx, |leader, window, cx| {
16802        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16803            s.select_ranges([1..1])
16804        });
16805        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
16806    });
16807    follower
16808        .update(cx, |follower, window, cx| {
16809            follower.apply_update_proto(
16810                &project,
16811                pending_update.borrow_mut().take().unwrap(),
16812                window,
16813                cx,
16814            )
16815        })
16816        .unwrap()
16817        .await
16818        .unwrap();
16819    _ = follower.update(cx, |follower, _, cx| {
16820        assert_eq!(
16821            follower.selections.ranges(&follower.display_snapshot(cx)),
16822            vec![0..0, 1..1]
16823        );
16824    });
16825    assert!(*is_still_following.borrow());
16826
16827    // Extend the pending selection so that it surrounds another selection
16828    _ = leader.update(cx, |leader, window, cx| {
16829        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
16830    });
16831    follower
16832        .update(cx, |follower, window, cx| {
16833            follower.apply_update_proto(
16834                &project,
16835                pending_update.borrow_mut().take().unwrap(),
16836                window,
16837                cx,
16838            )
16839        })
16840        .unwrap()
16841        .await
16842        .unwrap();
16843    _ = follower.update(cx, |follower, _, cx| {
16844        assert_eq!(
16845            follower.selections.ranges(&follower.display_snapshot(cx)),
16846            vec![0..2]
16847        );
16848    });
16849
16850    // Scrolling locally breaks the follow
16851    _ = follower.update(cx, |follower, window, cx| {
16852        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
16853        follower.set_scroll_anchor(
16854            ScrollAnchor {
16855                anchor: top_anchor,
16856                offset: gpui::Point::new(0.0, 0.5),
16857            },
16858            window,
16859            cx,
16860        );
16861    });
16862    assert!(!(*is_still_following.borrow()));
16863}
16864
16865#[gpui::test]
16866async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
16867    init_test(cx, |_| {});
16868
16869    let fs = FakeFs::new(cx.executor());
16870    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16871    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16872    let pane = workspace
16873        .update(cx, |workspace, _, _| workspace.active_pane().clone())
16874        .unwrap();
16875
16876    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16877
16878    let leader = pane.update_in(cx, |_, window, cx| {
16879        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
16880        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
16881    });
16882
16883    // Start following the editor when it has no excerpts.
16884    let mut state_message =
16885        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16886    let workspace_entity = workspace.root(cx).unwrap();
16887    let follower_1 = cx
16888        .update_window(*workspace.deref(), |_, window, cx| {
16889            Editor::from_state_proto(
16890                workspace_entity,
16891                ViewId {
16892                    creator: CollaboratorId::PeerId(PeerId::default()),
16893                    id: 0,
16894                },
16895                &mut state_message,
16896                window,
16897                cx,
16898            )
16899        })
16900        .unwrap()
16901        .unwrap()
16902        .await
16903        .unwrap();
16904
16905    let update_message = Rc::new(RefCell::new(None));
16906    follower_1.update_in(cx, {
16907        let update = update_message.clone();
16908        |_, window, cx| {
16909            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
16910                leader.read(cx).add_event_to_update_proto(
16911                    event,
16912                    &mut update.borrow_mut(),
16913                    window,
16914                    cx,
16915                );
16916            })
16917            .detach();
16918        }
16919    });
16920
16921    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
16922        (
16923            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, false, cx),
16924            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, false, cx),
16925        )
16926    });
16927
16928    // Insert some excerpts.
16929    leader.update(cx, |leader, cx| {
16930        leader.buffer.update(cx, |multibuffer, cx| {
16931            multibuffer.set_excerpts_for_path(
16932                PathKey::with_sort_prefix(1, rel_path("b.txt").into_arc()),
16933                buffer_1.clone(),
16934                vec![
16935                    Point::row_range(0..3),
16936                    Point::row_range(1..6),
16937                    Point::row_range(12..15),
16938                ],
16939                0,
16940                cx,
16941            );
16942            multibuffer.set_excerpts_for_path(
16943                PathKey::with_sort_prefix(1, rel_path("a.txt").into_arc()),
16944                buffer_2.clone(),
16945                vec![Point::row_range(0..6), Point::row_range(8..12)],
16946                0,
16947                cx,
16948            );
16949        });
16950    });
16951
16952    // Apply the update of adding the excerpts.
16953    follower_1
16954        .update_in(cx, |follower, window, cx| {
16955            follower.apply_update_proto(
16956                &project,
16957                update_message.borrow().clone().unwrap(),
16958                window,
16959                cx,
16960            )
16961        })
16962        .await
16963        .unwrap();
16964    assert_eq!(
16965        follower_1.update(cx, |editor, cx| editor.text(cx)),
16966        leader.update(cx, |editor, cx| editor.text(cx))
16967    );
16968    update_message.borrow_mut().take();
16969
16970    // Start following separately after it already has excerpts.
16971    let mut state_message =
16972        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16973    let workspace_entity = workspace.root(cx).unwrap();
16974    let follower_2 = cx
16975        .update_window(*workspace.deref(), |_, window, cx| {
16976            Editor::from_state_proto(
16977                workspace_entity,
16978                ViewId {
16979                    creator: CollaboratorId::PeerId(PeerId::default()),
16980                    id: 0,
16981                },
16982                &mut state_message,
16983                window,
16984                cx,
16985            )
16986        })
16987        .unwrap()
16988        .unwrap()
16989        .await
16990        .unwrap();
16991    assert_eq!(
16992        follower_2.update(cx, |editor, cx| editor.text(cx)),
16993        leader.update(cx, |editor, cx| editor.text(cx))
16994    );
16995
16996    // Remove some excerpts.
16997    leader.update(cx, |leader, cx| {
16998        leader.buffer.update(cx, |multibuffer, cx| {
16999            let excerpt_ids = multibuffer.excerpt_ids();
17000            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
17001            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
17002        });
17003    });
17004
17005    // Apply the update of removing the excerpts.
17006    follower_1
17007        .update_in(cx, |follower, window, cx| {
17008            follower.apply_update_proto(
17009                &project,
17010                update_message.borrow().clone().unwrap(),
17011                window,
17012                cx,
17013            )
17014        })
17015        .await
17016        .unwrap();
17017    follower_2
17018        .update_in(cx, |follower, window, cx| {
17019            follower.apply_update_proto(
17020                &project,
17021                update_message.borrow().clone().unwrap(),
17022                window,
17023                cx,
17024            )
17025        })
17026        .await
17027        .unwrap();
17028    update_message.borrow_mut().take();
17029    assert_eq!(
17030        follower_1.update(cx, |editor, cx| editor.text(cx)),
17031        leader.update(cx, |editor, cx| editor.text(cx))
17032    );
17033}
17034
17035#[gpui::test]
17036async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17037    init_test(cx, |_| {});
17038
17039    let mut cx = EditorTestContext::new(cx).await;
17040    let lsp_store =
17041        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
17042
17043    cx.set_state(indoc! {"
17044        ˇfn func(abc def: i32) -> u32 {
17045        }
17046    "});
17047
17048    cx.update(|_, cx| {
17049        lsp_store.update(cx, |lsp_store, cx| {
17050            lsp_store
17051                .update_diagnostics(
17052                    LanguageServerId(0),
17053                    lsp::PublishDiagnosticsParams {
17054                        uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
17055                        version: None,
17056                        diagnostics: vec![
17057                            lsp::Diagnostic {
17058                                range: lsp::Range::new(
17059                                    lsp::Position::new(0, 11),
17060                                    lsp::Position::new(0, 12),
17061                                ),
17062                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17063                                ..Default::default()
17064                            },
17065                            lsp::Diagnostic {
17066                                range: lsp::Range::new(
17067                                    lsp::Position::new(0, 12),
17068                                    lsp::Position::new(0, 15),
17069                                ),
17070                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17071                                ..Default::default()
17072                            },
17073                            lsp::Diagnostic {
17074                                range: lsp::Range::new(
17075                                    lsp::Position::new(0, 25),
17076                                    lsp::Position::new(0, 28),
17077                                ),
17078                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17079                                ..Default::default()
17080                            },
17081                        ],
17082                    },
17083                    None,
17084                    DiagnosticSourceKind::Pushed,
17085                    &[],
17086                    cx,
17087                )
17088                .unwrap()
17089        });
17090    });
17091
17092    executor.run_until_parked();
17093
17094    cx.update_editor(|editor, window, cx| {
17095        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17096    });
17097
17098    cx.assert_editor_state(indoc! {"
17099        fn func(abc def: i32) -> ˇu32 {
17100        }
17101    "});
17102
17103    cx.update_editor(|editor, window, cx| {
17104        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17105    });
17106
17107    cx.assert_editor_state(indoc! {"
17108        fn func(abc ˇdef: i32) -> u32 {
17109        }
17110    "});
17111
17112    cx.update_editor(|editor, window, cx| {
17113        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17114    });
17115
17116    cx.assert_editor_state(indoc! {"
17117        fn func(abcˇ def: i32) -> u32 {
17118        }
17119    "});
17120
17121    cx.update_editor(|editor, window, cx| {
17122        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17123    });
17124
17125    cx.assert_editor_state(indoc! {"
17126        fn func(abc def: i32) -> ˇu32 {
17127        }
17128    "});
17129}
17130
17131#[gpui::test]
17132async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17133    init_test(cx, |_| {});
17134
17135    let mut cx = EditorTestContext::new(cx).await;
17136
17137    let diff_base = r#"
17138        use some::mod;
17139
17140        const A: u32 = 42;
17141
17142        fn main() {
17143            println!("hello");
17144
17145            println!("world");
17146        }
17147        "#
17148    .unindent();
17149
17150    // Edits are modified, removed, modified, added
17151    cx.set_state(
17152        &r#"
17153        use some::modified;
17154
17155        ˇ
17156        fn main() {
17157            println!("hello there");
17158
17159            println!("around the");
17160            println!("world");
17161        }
17162        "#
17163        .unindent(),
17164    );
17165
17166    cx.set_head_text(&diff_base);
17167    executor.run_until_parked();
17168
17169    cx.update_editor(|editor, window, cx| {
17170        //Wrap around the bottom of the buffer
17171        for _ in 0..3 {
17172            editor.go_to_next_hunk(&GoToHunk, window, cx);
17173        }
17174    });
17175
17176    cx.assert_editor_state(
17177        &r#"
17178        ˇuse some::modified;
17179
17180
17181        fn main() {
17182            println!("hello there");
17183
17184            println!("around the");
17185            println!("world");
17186        }
17187        "#
17188        .unindent(),
17189    );
17190
17191    cx.update_editor(|editor, window, cx| {
17192        //Wrap around the top of the buffer
17193        for _ in 0..2 {
17194            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17195        }
17196    });
17197
17198    cx.assert_editor_state(
17199        &r#"
17200        use some::modified;
17201
17202
17203        fn main() {
17204        ˇ    println!("hello there");
17205
17206            println!("around the");
17207            println!("world");
17208        }
17209        "#
17210        .unindent(),
17211    );
17212
17213    cx.update_editor(|editor, window, cx| {
17214        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17215    });
17216
17217    cx.assert_editor_state(
17218        &r#"
17219        use some::modified;
17220
17221        ˇ
17222        fn main() {
17223            println!("hello there");
17224
17225            println!("around the");
17226            println!("world");
17227        }
17228        "#
17229        .unindent(),
17230    );
17231
17232    cx.update_editor(|editor, window, cx| {
17233        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17234    });
17235
17236    cx.assert_editor_state(
17237        &r#"
17238        ˇuse some::modified;
17239
17240
17241        fn main() {
17242            println!("hello there");
17243
17244            println!("around the");
17245            println!("world");
17246        }
17247        "#
17248        .unindent(),
17249    );
17250
17251    cx.update_editor(|editor, window, cx| {
17252        for _ in 0..2 {
17253            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17254        }
17255    });
17256
17257    cx.assert_editor_state(
17258        &r#"
17259        use some::modified;
17260
17261
17262        fn main() {
17263        ˇ    println!("hello there");
17264
17265            println!("around the");
17266            println!("world");
17267        }
17268        "#
17269        .unindent(),
17270    );
17271
17272    cx.update_editor(|editor, window, cx| {
17273        editor.fold(&Fold, window, cx);
17274    });
17275
17276    cx.update_editor(|editor, window, cx| {
17277        editor.go_to_next_hunk(&GoToHunk, window, cx);
17278    });
17279
17280    cx.assert_editor_state(
17281        &r#"
17282        ˇuse some::modified;
17283
17284
17285        fn main() {
17286            println!("hello there");
17287
17288            println!("around the");
17289            println!("world");
17290        }
17291        "#
17292        .unindent(),
17293    );
17294}
17295
17296#[test]
17297fn test_split_words() {
17298    fn split(text: &str) -> Vec<&str> {
17299        split_words(text).collect()
17300    }
17301
17302    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
17303    assert_eq!(split("hello_world"), &["hello_", "world"]);
17304    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
17305    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
17306    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
17307    assert_eq!(split("helloworld"), &["helloworld"]);
17308
17309    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
17310}
17311
17312#[gpui::test]
17313async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
17314    init_test(cx, |_| {});
17315
17316    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
17317    let mut assert = |before, after| {
17318        let _state_context = cx.set_state(before);
17319        cx.run_until_parked();
17320        cx.update_editor(|editor, window, cx| {
17321            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
17322        });
17323        cx.run_until_parked();
17324        cx.assert_editor_state(after);
17325    };
17326
17327    // Outside bracket jumps to outside of matching bracket
17328    assert("console.logˇ(var);", "console.log(var)ˇ;");
17329    assert("console.log(var)ˇ;", "console.logˇ(var);");
17330
17331    // Inside bracket jumps to inside of matching bracket
17332    assert("console.log(ˇvar);", "console.log(varˇ);");
17333    assert("console.log(varˇ);", "console.log(ˇvar);");
17334
17335    // When outside a bracket and inside, favor jumping to the inside bracket
17336    assert(
17337        "console.log('foo', [1, 2, 3]ˇ);",
17338        "console.log(ˇ'foo', [1, 2, 3]);",
17339    );
17340    assert(
17341        "console.log(ˇ'foo', [1, 2, 3]);",
17342        "console.log('foo', [1, 2, 3]ˇ);",
17343    );
17344
17345    // Bias forward if two options are equally likely
17346    assert(
17347        "let result = curried_fun()ˇ();",
17348        "let result = curried_fun()()ˇ;",
17349    );
17350
17351    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
17352    assert(
17353        indoc! {"
17354            function test() {
17355                console.log('test')ˇ
17356            }"},
17357        indoc! {"
17358            function test() {
17359                console.logˇ('test')
17360            }"},
17361    );
17362}
17363
17364#[gpui::test]
17365async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
17366    init_test(cx, |_| {});
17367
17368    let fs = FakeFs::new(cx.executor());
17369    fs.insert_tree(
17370        path!("/a"),
17371        json!({
17372            "main.rs": "fn main() { let a = 5; }",
17373            "other.rs": "// Test file",
17374        }),
17375    )
17376    .await;
17377    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17378
17379    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17380    language_registry.add(Arc::new(Language::new(
17381        LanguageConfig {
17382            name: "Rust".into(),
17383            matcher: LanguageMatcher {
17384                path_suffixes: vec!["rs".to_string()],
17385                ..Default::default()
17386            },
17387            brackets: BracketPairConfig {
17388                pairs: vec![BracketPair {
17389                    start: "{".to_string(),
17390                    end: "}".to_string(),
17391                    close: true,
17392                    surround: true,
17393                    newline: true,
17394                }],
17395                disabled_scopes_by_bracket_ix: Vec::new(),
17396            },
17397            ..Default::default()
17398        },
17399        Some(tree_sitter_rust::LANGUAGE.into()),
17400    )));
17401    let mut fake_servers = language_registry.register_fake_lsp(
17402        "Rust",
17403        FakeLspAdapter {
17404            capabilities: lsp::ServerCapabilities {
17405                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
17406                    first_trigger_character: "{".to_string(),
17407                    more_trigger_character: None,
17408                }),
17409                ..Default::default()
17410            },
17411            ..Default::default()
17412        },
17413    );
17414
17415    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17416
17417    let cx = &mut VisualTestContext::from_window(*workspace, cx);
17418
17419    let worktree_id = workspace
17420        .update(cx, |workspace, _, cx| {
17421            workspace.project().update(cx, |project, cx| {
17422                project.worktrees(cx).next().unwrap().read(cx).id()
17423            })
17424        })
17425        .unwrap();
17426
17427    let buffer = project
17428        .update(cx, |project, cx| {
17429            project.open_local_buffer(path!("/a/main.rs"), cx)
17430        })
17431        .await
17432        .unwrap();
17433    let editor_handle = workspace
17434        .update(cx, |workspace, window, cx| {
17435            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
17436        })
17437        .unwrap()
17438        .await
17439        .unwrap()
17440        .downcast::<Editor>()
17441        .unwrap();
17442
17443    cx.executor().start_waiting();
17444    let fake_server = fake_servers.next().await.unwrap();
17445
17446    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
17447        |params, _| async move {
17448            assert_eq!(
17449                params.text_document_position.text_document.uri,
17450                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
17451            );
17452            assert_eq!(
17453                params.text_document_position.position,
17454                lsp::Position::new(0, 21),
17455            );
17456
17457            Ok(Some(vec![lsp::TextEdit {
17458                new_text: "]".to_string(),
17459                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17460            }]))
17461        },
17462    );
17463
17464    editor_handle.update_in(cx, |editor, window, cx| {
17465        window.focus(&editor.focus_handle(cx));
17466        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17467            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
17468        });
17469        editor.handle_input("{", window, cx);
17470    });
17471
17472    cx.executor().run_until_parked();
17473
17474    buffer.update(cx, |buffer, _| {
17475        assert_eq!(
17476            buffer.text(),
17477            "fn main() { let a = {5}; }",
17478            "No extra braces from on type formatting should appear in the buffer"
17479        )
17480    });
17481}
17482
17483#[gpui::test(iterations = 20, seeds(31))]
17484async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
17485    init_test(cx, |_| {});
17486
17487    let mut cx = EditorLspTestContext::new_rust(
17488        lsp::ServerCapabilities {
17489            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
17490                first_trigger_character: ".".to_string(),
17491                more_trigger_character: None,
17492            }),
17493            ..Default::default()
17494        },
17495        cx,
17496    )
17497    .await;
17498
17499    cx.update_buffer(|buffer, _| {
17500        // This causes autoindent to be async.
17501        buffer.set_sync_parse_timeout(Duration::ZERO)
17502    });
17503
17504    cx.set_state("fn c() {\n    d()ˇ\n}\n");
17505    cx.simulate_keystroke("\n");
17506    cx.run_until_parked();
17507
17508    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
17509    let mut request =
17510        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
17511            let buffer_cloned = buffer_cloned.clone();
17512            async move {
17513                buffer_cloned.update(&mut cx, |buffer, _| {
17514                    assert_eq!(
17515                        buffer.text(),
17516                        "fn c() {\n    d()\n        .\n}\n",
17517                        "OnTypeFormatting should triggered after autoindent applied"
17518                    )
17519                })?;
17520
17521                Ok(Some(vec![]))
17522            }
17523        });
17524
17525    cx.simulate_keystroke(".");
17526    cx.run_until_parked();
17527
17528    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
17529    assert!(request.next().await.is_some());
17530    request.close();
17531    assert!(request.next().await.is_none());
17532}
17533
17534#[gpui::test]
17535async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
17536    init_test(cx, |_| {});
17537
17538    let fs = FakeFs::new(cx.executor());
17539    fs.insert_tree(
17540        path!("/a"),
17541        json!({
17542            "main.rs": "fn main() { let a = 5; }",
17543            "other.rs": "// Test file",
17544        }),
17545    )
17546    .await;
17547
17548    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17549
17550    let server_restarts = Arc::new(AtomicUsize::new(0));
17551    let closure_restarts = Arc::clone(&server_restarts);
17552    let language_server_name = "test language server";
17553    let language_name: LanguageName = "Rust".into();
17554
17555    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17556    language_registry.add(Arc::new(Language::new(
17557        LanguageConfig {
17558            name: language_name.clone(),
17559            matcher: LanguageMatcher {
17560                path_suffixes: vec!["rs".to_string()],
17561                ..Default::default()
17562            },
17563            ..Default::default()
17564        },
17565        Some(tree_sitter_rust::LANGUAGE.into()),
17566    )));
17567    let mut fake_servers = language_registry.register_fake_lsp(
17568        "Rust",
17569        FakeLspAdapter {
17570            name: language_server_name,
17571            initialization_options: Some(json!({
17572                "testOptionValue": true
17573            })),
17574            initializer: Some(Box::new(move |fake_server| {
17575                let task_restarts = Arc::clone(&closure_restarts);
17576                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
17577                    task_restarts.fetch_add(1, atomic::Ordering::Release);
17578                    futures::future::ready(Ok(()))
17579                });
17580            })),
17581            ..Default::default()
17582        },
17583    );
17584
17585    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17586    let _buffer = project
17587        .update(cx, |project, cx| {
17588            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
17589        })
17590        .await
17591        .unwrap();
17592    let _fake_server = fake_servers.next().await.unwrap();
17593    update_test_language_settings(cx, |language_settings| {
17594        language_settings.languages.0.insert(
17595            language_name.clone().0,
17596            LanguageSettingsContent {
17597                tab_size: NonZeroU32::new(8),
17598                ..Default::default()
17599            },
17600        );
17601    });
17602    cx.executor().run_until_parked();
17603    assert_eq!(
17604        server_restarts.load(atomic::Ordering::Acquire),
17605        0,
17606        "Should not restart LSP server on an unrelated change"
17607    );
17608
17609    update_test_project_settings(cx, |project_settings| {
17610        project_settings.lsp.insert(
17611            "Some other server name".into(),
17612            LspSettings {
17613                binary: None,
17614                settings: None,
17615                initialization_options: Some(json!({
17616                    "some other init value": false
17617                })),
17618                enable_lsp_tasks: false,
17619                fetch: None,
17620            },
17621        );
17622    });
17623    cx.executor().run_until_parked();
17624    assert_eq!(
17625        server_restarts.load(atomic::Ordering::Acquire),
17626        0,
17627        "Should not restart LSP server on an unrelated LSP settings change"
17628    );
17629
17630    update_test_project_settings(cx, |project_settings| {
17631        project_settings.lsp.insert(
17632            language_server_name.into(),
17633            LspSettings {
17634                binary: None,
17635                settings: None,
17636                initialization_options: Some(json!({
17637                    "anotherInitValue": false
17638                })),
17639                enable_lsp_tasks: false,
17640                fetch: None,
17641            },
17642        );
17643    });
17644    cx.executor().run_until_parked();
17645    assert_eq!(
17646        server_restarts.load(atomic::Ordering::Acquire),
17647        1,
17648        "Should restart LSP server on a related LSP settings change"
17649    );
17650
17651    update_test_project_settings(cx, |project_settings| {
17652        project_settings.lsp.insert(
17653            language_server_name.into(),
17654            LspSettings {
17655                binary: None,
17656                settings: None,
17657                initialization_options: Some(json!({
17658                    "anotherInitValue": false
17659                })),
17660                enable_lsp_tasks: false,
17661                fetch: None,
17662            },
17663        );
17664    });
17665    cx.executor().run_until_parked();
17666    assert_eq!(
17667        server_restarts.load(atomic::Ordering::Acquire),
17668        1,
17669        "Should not restart LSP server on a related LSP settings change that is the same"
17670    );
17671
17672    update_test_project_settings(cx, |project_settings| {
17673        project_settings.lsp.insert(
17674            language_server_name.into(),
17675            LspSettings {
17676                binary: None,
17677                settings: None,
17678                initialization_options: None,
17679                enable_lsp_tasks: false,
17680                fetch: None,
17681            },
17682        );
17683    });
17684    cx.executor().run_until_parked();
17685    assert_eq!(
17686        server_restarts.load(atomic::Ordering::Acquire),
17687        2,
17688        "Should restart LSP server on another related LSP settings change"
17689    );
17690}
17691
17692#[gpui::test]
17693async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
17694    init_test(cx, |_| {});
17695
17696    let mut cx = EditorLspTestContext::new_rust(
17697        lsp::ServerCapabilities {
17698            completion_provider: Some(lsp::CompletionOptions {
17699                trigger_characters: Some(vec![".".to_string()]),
17700                resolve_provider: Some(true),
17701                ..Default::default()
17702            }),
17703            ..Default::default()
17704        },
17705        cx,
17706    )
17707    .await;
17708
17709    cx.set_state("fn main() { let a = 2ˇ; }");
17710    cx.simulate_keystroke(".");
17711    let completion_item = lsp::CompletionItem {
17712        label: "some".into(),
17713        kind: Some(lsp::CompletionItemKind::SNIPPET),
17714        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
17715        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
17716            kind: lsp::MarkupKind::Markdown,
17717            value: "```rust\nSome(2)\n```".to_string(),
17718        })),
17719        deprecated: Some(false),
17720        sort_text: Some("fffffff2".to_string()),
17721        filter_text: Some("some".to_string()),
17722        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
17723        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17724            range: lsp::Range {
17725                start: lsp::Position {
17726                    line: 0,
17727                    character: 22,
17728                },
17729                end: lsp::Position {
17730                    line: 0,
17731                    character: 22,
17732                },
17733            },
17734            new_text: "Some(2)".to_string(),
17735        })),
17736        additional_text_edits: Some(vec![lsp::TextEdit {
17737            range: lsp::Range {
17738                start: lsp::Position {
17739                    line: 0,
17740                    character: 20,
17741                },
17742                end: lsp::Position {
17743                    line: 0,
17744                    character: 22,
17745                },
17746            },
17747            new_text: "".to_string(),
17748        }]),
17749        ..Default::default()
17750    };
17751
17752    let closure_completion_item = completion_item.clone();
17753    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17754        let task_completion_item = closure_completion_item.clone();
17755        async move {
17756            Ok(Some(lsp::CompletionResponse::Array(vec![
17757                task_completion_item,
17758            ])))
17759        }
17760    });
17761
17762    request.next().await;
17763
17764    cx.condition(|editor, _| editor.context_menu_visible())
17765        .await;
17766    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
17767        editor
17768            .confirm_completion(&ConfirmCompletion::default(), window, cx)
17769            .unwrap()
17770    });
17771    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
17772
17773    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
17774        let task_completion_item = completion_item.clone();
17775        async move { Ok(task_completion_item) }
17776    })
17777    .next()
17778    .await
17779    .unwrap();
17780    apply_additional_edits.await.unwrap();
17781    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
17782}
17783
17784#[gpui::test]
17785async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
17786    init_test(cx, |_| {});
17787
17788    let mut cx = EditorLspTestContext::new_rust(
17789        lsp::ServerCapabilities {
17790            completion_provider: Some(lsp::CompletionOptions {
17791                trigger_characters: Some(vec![".".to_string()]),
17792                resolve_provider: Some(true),
17793                ..Default::default()
17794            }),
17795            ..Default::default()
17796        },
17797        cx,
17798    )
17799    .await;
17800
17801    cx.set_state("fn main() { let a = 2ˇ; }");
17802    cx.simulate_keystroke(".");
17803
17804    let item1 = lsp::CompletionItem {
17805        label: "method id()".to_string(),
17806        filter_text: Some("id".to_string()),
17807        detail: None,
17808        documentation: None,
17809        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17810            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17811            new_text: ".id".to_string(),
17812        })),
17813        ..lsp::CompletionItem::default()
17814    };
17815
17816    let item2 = lsp::CompletionItem {
17817        label: "other".to_string(),
17818        filter_text: Some("other".to_string()),
17819        detail: None,
17820        documentation: None,
17821        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17822            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17823            new_text: ".other".to_string(),
17824        })),
17825        ..lsp::CompletionItem::default()
17826    };
17827
17828    let item1 = item1.clone();
17829    cx.set_request_handler::<lsp::request::Completion, _, _>({
17830        let item1 = item1.clone();
17831        move |_, _, _| {
17832            let item1 = item1.clone();
17833            let item2 = item2.clone();
17834            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
17835        }
17836    })
17837    .next()
17838    .await;
17839
17840    cx.condition(|editor, _| editor.context_menu_visible())
17841        .await;
17842    cx.update_editor(|editor, _, _| {
17843        let context_menu = editor.context_menu.borrow_mut();
17844        let context_menu = context_menu
17845            .as_ref()
17846            .expect("Should have the context menu deployed");
17847        match context_menu {
17848            CodeContextMenu::Completions(completions_menu) => {
17849                let completions = completions_menu.completions.borrow_mut();
17850                assert_eq!(
17851                    completions
17852                        .iter()
17853                        .map(|completion| &completion.label.text)
17854                        .collect::<Vec<_>>(),
17855                    vec!["method id()", "other"]
17856                )
17857            }
17858            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17859        }
17860    });
17861
17862    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
17863        let item1 = item1.clone();
17864        move |_, item_to_resolve, _| {
17865            let item1 = item1.clone();
17866            async move {
17867                if item1 == item_to_resolve {
17868                    Ok(lsp::CompletionItem {
17869                        label: "method id()".to_string(),
17870                        filter_text: Some("id".to_string()),
17871                        detail: Some("Now resolved!".to_string()),
17872                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
17873                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17874                            range: lsp::Range::new(
17875                                lsp::Position::new(0, 22),
17876                                lsp::Position::new(0, 22),
17877                            ),
17878                            new_text: ".id".to_string(),
17879                        })),
17880                        ..lsp::CompletionItem::default()
17881                    })
17882                } else {
17883                    Ok(item_to_resolve)
17884                }
17885            }
17886        }
17887    })
17888    .next()
17889    .await
17890    .unwrap();
17891    cx.run_until_parked();
17892
17893    cx.update_editor(|editor, window, cx| {
17894        editor.context_menu_next(&Default::default(), window, cx);
17895    });
17896
17897    cx.update_editor(|editor, _, _| {
17898        let context_menu = editor.context_menu.borrow_mut();
17899        let context_menu = context_menu
17900            .as_ref()
17901            .expect("Should have the context menu deployed");
17902        match context_menu {
17903            CodeContextMenu::Completions(completions_menu) => {
17904                let completions = completions_menu.completions.borrow_mut();
17905                assert_eq!(
17906                    completions
17907                        .iter()
17908                        .map(|completion| &completion.label.text)
17909                        .collect::<Vec<_>>(),
17910                    vec!["method id() Now resolved!", "other"],
17911                    "Should update first completion label, but not second as the filter text did not match."
17912                );
17913            }
17914            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17915        }
17916    });
17917}
17918
17919#[gpui::test]
17920async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
17921    init_test(cx, |_| {});
17922    let mut cx = EditorLspTestContext::new_rust(
17923        lsp::ServerCapabilities {
17924            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
17925            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
17926            completion_provider: Some(lsp::CompletionOptions {
17927                resolve_provider: Some(true),
17928                ..Default::default()
17929            }),
17930            ..Default::default()
17931        },
17932        cx,
17933    )
17934    .await;
17935    cx.set_state(indoc! {"
17936        struct TestStruct {
17937            field: i32
17938        }
17939
17940        fn mainˇ() {
17941            let unused_var = 42;
17942            let test_struct = TestStruct { field: 42 };
17943        }
17944    "});
17945    let symbol_range = cx.lsp_range(indoc! {"
17946        struct TestStruct {
17947            field: i32
17948        }
17949
17950        «fn main»() {
17951            let unused_var = 42;
17952            let test_struct = TestStruct { field: 42 };
17953        }
17954    "});
17955    let mut hover_requests =
17956        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
17957            Ok(Some(lsp::Hover {
17958                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
17959                    kind: lsp::MarkupKind::Markdown,
17960                    value: "Function documentation".to_string(),
17961                }),
17962                range: Some(symbol_range),
17963            }))
17964        });
17965
17966    // Case 1: Test that code action menu hide hover popover
17967    cx.dispatch_action(Hover);
17968    hover_requests.next().await;
17969    cx.condition(|editor, _| editor.hover_state.visible()).await;
17970    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
17971        move |_, _, _| async move {
17972            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
17973                lsp::CodeAction {
17974                    title: "Remove unused variable".to_string(),
17975                    kind: Some(CodeActionKind::QUICKFIX),
17976                    edit: Some(lsp::WorkspaceEdit {
17977                        changes: Some(
17978                            [(
17979                                lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
17980                                vec![lsp::TextEdit {
17981                                    range: lsp::Range::new(
17982                                        lsp::Position::new(5, 4),
17983                                        lsp::Position::new(5, 27),
17984                                    ),
17985                                    new_text: "".to_string(),
17986                                }],
17987                            )]
17988                            .into_iter()
17989                            .collect(),
17990                        ),
17991                        ..Default::default()
17992                    }),
17993                    ..Default::default()
17994                },
17995            )]))
17996        },
17997    );
17998    cx.update_editor(|editor, window, cx| {
17999        editor.toggle_code_actions(
18000            &ToggleCodeActions {
18001                deployed_from: None,
18002                quick_launch: false,
18003            },
18004            window,
18005            cx,
18006        );
18007    });
18008    code_action_requests.next().await;
18009    cx.run_until_parked();
18010    cx.condition(|editor, _| editor.context_menu_visible())
18011        .await;
18012    cx.update_editor(|editor, _, _| {
18013        assert!(
18014            !editor.hover_state.visible(),
18015            "Hover popover should be hidden when code action menu is shown"
18016        );
18017        // Hide code actions
18018        editor.context_menu.take();
18019    });
18020
18021    // Case 2: Test that code completions hide hover popover
18022    cx.dispatch_action(Hover);
18023    hover_requests.next().await;
18024    cx.condition(|editor, _| editor.hover_state.visible()).await;
18025    let counter = Arc::new(AtomicUsize::new(0));
18026    let mut completion_requests =
18027        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18028            let counter = counter.clone();
18029            async move {
18030                counter.fetch_add(1, atomic::Ordering::Release);
18031                Ok(Some(lsp::CompletionResponse::Array(vec![
18032                    lsp::CompletionItem {
18033                        label: "main".into(),
18034                        kind: Some(lsp::CompletionItemKind::FUNCTION),
18035                        detail: Some("() -> ()".to_string()),
18036                        ..Default::default()
18037                    },
18038                    lsp::CompletionItem {
18039                        label: "TestStruct".into(),
18040                        kind: Some(lsp::CompletionItemKind::STRUCT),
18041                        detail: Some("struct TestStruct".to_string()),
18042                        ..Default::default()
18043                    },
18044                ])))
18045            }
18046        });
18047    cx.update_editor(|editor, window, cx| {
18048        editor.show_completions(&ShowCompletions, window, cx);
18049    });
18050    completion_requests.next().await;
18051    cx.condition(|editor, _| editor.context_menu_visible())
18052        .await;
18053    cx.update_editor(|editor, _, _| {
18054        assert!(
18055            !editor.hover_state.visible(),
18056            "Hover popover should be hidden when completion menu is shown"
18057        );
18058    });
18059}
18060
18061#[gpui::test]
18062async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
18063    init_test(cx, |_| {});
18064
18065    let mut cx = EditorLspTestContext::new_rust(
18066        lsp::ServerCapabilities {
18067            completion_provider: Some(lsp::CompletionOptions {
18068                trigger_characters: Some(vec![".".to_string()]),
18069                resolve_provider: Some(true),
18070                ..Default::default()
18071            }),
18072            ..Default::default()
18073        },
18074        cx,
18075    )
18076    .await;
18077
18078    cx.set_state("fn main() { let a = 2ˇ; }");
18079    cx.simulate_keystroke(".");
18080
18081    let unresolved_item_1 = lsp::CompletionItem {
18082        label: "id".to_string(),
18083        filter_text: Some("id".to_string()),
18084        detail: None,
18085        documentation: None,
18086        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18087            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18088            new_text: ".id".to_string(),
18089        })),
18090        ..lsp::CompletionItem::default()
18091    };
18092    let resolved_item_1 = lsp::CompletionItem {
18093        additional_text_edits: Some(vec![lsp::TextEdit {
18094            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
18095            new_text: "!!".to_string(),
18096        }]),
18097        ..unresolved_item_1.clone()
18098    };
18099    let unresolved_item_2 = lsp::CompletionItem {
18100        label: "other".to_string(),
18101        filter_text: Some("other".to_string()),
18102        detail: None,
18103        documentation: None,
18104        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18105            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18106            new_text: ".other".to_string(),
18107        })),
18108        ..lsp::CompletionItem::default()
18109    };
18110    let resolved_item_2 = lsp::CompletionItem {
18111        additional_text_edits: Some(vec![lsp::TextEdit {
18112            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
18113            new_text: "??".to_string(),
18114        }]),
18115        ..unresolved_item_2.clone()
18116    };
18117
18118    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
18119    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
18120    cx.lsp
18121        .server
18122        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
18123            let unresolved_item_1 = unresolved_item_1.clone();
18124            let resolved_item_1 = resolved_item_1.clone();
18125            let unresolved_item_2 = unresolved_item_2.clone();
18126            let resolved_item_2 = resolved_item_2.clone();
18127            let resolve_requests_1 = resolve_requests_1.clone();
18128            let resolve_requests_2 = resolve_requests_2.clone();
18129            move |unresolved_request, _| {
18130                let unresolved_item_1 = unresolved_item_1.clone();
18131                let resolved_item_1 = resolved_item_1.clone();
18132                let unresolved_item_2 = unresolved_item_2.clone();
18133                let resolved_item_2 = resolved_item_2.clone();
18134                let resolve_requests_1 = resolve_requests_1.clone();
18135                let resolve_requests_2 = resolve_requests_2.clone();
18136                async move {
18137                    if unresolved_request == unresolved_item_1 {
18138                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
18139                        Ok(resolved_item_1.clone())
18140                    } else if unresolved_request == unresolved_item_2 {
18141                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
18142                        Ok(resolved_item_2.clone())
18143                    } else {
18144                        panic!("Unexpected completion item {unresolved_request:?}")
18145                    }
18146                }
18147            }
18148        })
18149        .detach();
18150
18151    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18152        let unresolved_item_1 = unresolved_item_1.clone();
18153        let unresolved_item_2 = unresolved_item_2.clone();
18154        async move {
18155            Ok(Some(lsp::CompletionResponse::Array(vec![
18156                unresolved_item_1,
18157                unresolved_item_2,
18158            ])))
18159        }
18160    })
18161    .next()
18162    .await;
18163
18164    cx.condition(|editor, _| editor.context_menu_visible())
18165        .await;
18166    cx.update_editor(|editor, _, _| {
18167        let context_menu = editor.context_menu.borrow_mut();
18168        let context_menu = context_menu
18169            .as_ref()
18170            .expect("Should have the context menu deployed");
18171        match context_menu {
18172            CodeContextMenu::Completions(completions_menu) => {
18173                let completions = completions_menu.completions.borrow_mut();
18174                assert_eq!(
18175                    completions
18176                        .iter()
18177                        .map(|completion| &completion.label.text)
18178                        .collect::<Vec<_>>(),
18179                    vec!["id", "other"]
18180                )
18181            }
18182            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
18183        }
18184    });
18185    cx.run_until_parked();
18186
18187    cx.update_editor(|editor, window, cx| {
18188        editor.context_menu_next(&ContextMenuNext, window, cx);
18189    });
18190    cx.run_until_parked();
18191    cx.update_editor(|editor, window, cx| {
18192        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
18193    });
18194    cx.run_until_parked();
18195    cx.update_editor(|editor, window, cx| {
18196        editor.context_menu_next(&ContextMenuNext, window, cx);
18197    });
18198    cx.run_until_parked();
18199    cx.update_editor(|editor, window, cx| {
18200        editor
18201            .compose_completion(&ComposeCompletion::default(), window, cx)
18202            .expect("No task returned")
18203    })
18204    .await
18205    .expect("Completion failed");
18206    cx.run_until_parked();
18207
18208    cx.update_editor(|editor, _, cx| {
18209        assert_eq!(
18210            resolve_requests_1.load(atomic::Ordering::Acquire),
18211            1,
18212            "Should always resolve once despite multiple selections"
18213        );
18214        assert_eq!(
18215            resolve_requests_2.load(atomic::Ordering::Acquire),
18216            1,
18217            "Should always resolve once after multiple selections and applying the completion"
18218        );
18219        assert_eq!(
18220            editor.text(cx),
18221            "fn main() { let a = ??.other; }",
18222            "Should use resolved data when applying the completion"
18223        );
18224    });
18225}
18226
18227#[gpui::test]
18228async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
18229    init_test(cx, |_| {});
18230
18231    let item_0 = lsp::CompletionItem {
18232        label: "abs".into(),
18233        insert_text: Some("abs".into()),
18234        data: Some(json!({ "very": "special"})),
18235        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
18236        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
18237            lsp::InsertReplaceEdit {
18238                new_text: "abs".to_string(),
18239                insert: lsp::Range::default(),
18240                replace: lsp::Range::default(),
18241            },
18242        )),
18243        ..lsp::CompletionItem::default()
18244    };
18245    let items = iter::once(item_0.clone())
18246        .chain((11..51).map(|i| lsp::CompletionItem {
18247            label: format!("item_{}", i),
18248            insert_text: Some(format!("item_{}", i)),
18249            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
18250            ..lsp::CompletionItem::default()
18251        }))
18252        .collect::<Vec<_>>();
18253
18254    let default_commit_characters = vec!["?".to_string()];
18255    let default_data = json!({ "default": "data"});
18256    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
18257    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
18258    let default_edit_range = lsp::Range {
18259        start: lsp::Position {
18260            line: 0,
18261            character: 5,
18262        },
18263        end: lsp::Position {
18264            line: 0,
18265            character: 5,
18266        },
18267    };
18268
18269    let mut cx = EditorLspTestContext::new_rust(
18270        lsp::ServerCapabilities {
18271            completion_provider: Some(lsp::CompletionOptions {
18272                trigger_characters: Some(vec![".".to_string()]),
18273                resolve_provider: Some(true),
18274                ..Default::default()
18275            }),
18276            ..Default::default()
18277        },
18278        cx,
18279    )
18280    .await;
18281
18282    cx.set_state("fn main() { let a = 2ˇ; }");
18283    cx.simulate_keystroke(".");
18284
18285    let completion_data = default_data.clone();
18286    let completion_characters = default_commit_characters.clone();
18287    let completion_items = items.clone();
18288    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18289        let default_data = completion_data.clone();
18290        let default_commit_characters = completion_characters.clone();
18291        let items = completion_items.clone();
18292        async move {
18293            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
18294                items,
18295                item_defaults: Some(lsp::CompletionListItemDefaults {
18296                    data: Some(default_data.clone()),
18297                    commit_characters: Some(default_commit_characters.clone()),
18298                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
18299                        default_edit_range,
18300                    )),
18301                    insert_text_format: Some(default_insert_text_format),
18302                    insert_text_mode: Some(default_insert_text_mode),
18303                }),
18304                ..lsp::CompletionList::default()
18305            })))
18306        }
18307    })
18308    .next()
18309    .await;
18310
18311    let resolved_items = Arc::new(Mutex::new(Vec::new()));
18312    cx.lsp
18313        .server
18314        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
18315            let closure_resolved_items = resolved_items.clone();
18316            move |item_to_resolve, _| {
18317                let closure_resolved_items = closure_resolved_items.clone();
18318                async move {
18319                    closure_resolved_items.lock().push(item_to_resolve.clone());
18320                    Ok(item_to_resolve)
18321                }
18322            }
18323        })
18324        .detach();
18325
18326    cx.condition(|editor, _| editor.context_menu_visible())
18327        .await;
18328    cx.run_until_parked();
18329    cx.update_editor(|editor, _, _| {
18330        let menu = editor.context_menu.borrow_mut();
18331        match menu.as_ref().expect("should have the completions menu") {
18332            CodeContextMenu::Completions(completions_menu) => {
18333                assert_eq!(
18334                    completions_menu
18335                        .entries
18336                        .borrow()
18337                        .iter()
18338                        .map(|mat| mat.string.clone())
18339                        .collect::<Vec<String>>(),
18340                    items
18341                        .iter()
18342                        .map(|completion| completion.label.clone())
18343                        .collect::<Vec<String>>()
18344                );
18345            }
18346            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
18347        }
18348    });
18349    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
18350    // with 4 from the end.
18351    assert_eq!(
18352        *resolved_items.lock(),
18353        [&items[0..16], &items[items.len() - 4..items.len()]]
18354            .concat()
18355            .iter()
18356            .cloned()
18357            .map(|mut item| {
18358                if item.data.is_none() {
18359                    item.data = Some(default_data.clone());
18360                }
18361                item
18362            })
18363            .collect::<Vec<lsp::CompletionItem>>(),
18364        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
18365    );
18366    resolved_items.lock().clear();
18367
18368    cx.update_editor(|editor, window, cx| {
18369        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
18370    });
18371    cx.run_until_parked();
18372    // Completions that have already been resolved are skipped.
18373    assert_eq!(
18374        *resolved_items.lock(),
18375        items[items.len() - 17..items.len() - 4]
18376            .iter()
18377            .cloned()
18378            .map(|mut item| {
18379                if item.data.is_none() {
18380                    item.data = Some(default_data.clone());
18381                }
18382                item
18383            })
18384            .collect::<Vec<lsp::CompletionItem>>()
18385    );
18386    resolved_items.lock().clear();
18387}
18388
18389#[gpui::test]
18390async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
18391    init_test(cx, |_| {});
18392
18393    let mut cx = EditorLspTestContext::new(
18394        Language::new(
18395            LanguageConfig {
18396                matcher: LanguageMatcher {
18397                    path_suffixes: vec!["jsx".into()],
18398                    ..Default::default()
18399                },
18400                overrides: [(
18401                    "element".into(),
18402                    LanguageConfigOverride {
18403                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
18404                        ..Default::default()
18405                    },
18406                )]
18407                .into_iter()
18408                .collect(),
18409                ..Default::default()
18410            },
18411            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
18412        )
18413        .with_override_query("(jsx_self_closing_element) @element")
18414        .unwrap(),
18415        lsp::ServerCapabilities {
18416            completion_provider: Some(lsp::CompletionOptions {
18417                trigger_characters: Some(vec![":".to_string()]),
18418                ..Default::default()
18419            }),
18420            ..Default::default()
18421        },
18422        cx,
18423    )
18424    .await;
18425
18426    cx.lsp
18427        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
18428            Ok(Some(lsp::CompletionResponse::Array(vec![
18429                lsp::CompletionItem {
18430                    label: "bg-blue".into(),
18431                    ..Default::default()
18432                },
18433                lsp::CompletionItem {
18434                    label: "bg-red".into(),
18435                    ..Default::default()
18436                },
18437                lsp::CompletionItem {
18438                    label: "bg-yellow".into(),
18439                    ..Default::default()
18440                },
18441            ])))
18442        });
18443
18444    cx.set_state(r#"<p class="bgˇ" />"#);
18445
18446    // Trigger completion when typing a dash, because the dash is an extra
18447    // word character in the 'element' scope, which contains the cursor.
18448    cx.simulate_keystroke("-");
18449    cx.executor().run_until_parked();
18450    cx.update_editor(|editor, _, _| {
18451        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18452        {
18453            assert_eq!(
18454                completion_menu_entries(menu),
18455                &["bg-blue", "bg-red", "bg-yellow"]
18456            );
18457        } else {
18458            panic!("expected completion menu to be open");
18459        }
18460    });
18461
18462    cx.simulate_keystroke("l");
18463    cx.executor().run_until_parked();
18464    cx.update_editor(|editor, _, _| {
18465        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18466        {
18467            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
18468        } else {
18469            panic!("expected completion menu to be open");
18470        }
18471    });
18472
18473    // When filtering completions, consider the character after the '-' to
18474    // be the start of a subword.
18475    cx.set_state(r#"<p class="yelˇ" />"#);
18476    cx.simulate_keystroke("l");
18477    cx.executor().run_until_parked();
18478    cx.update_editor(|editor, _, _| {
18479        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18480        {
18481            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
18482        } else {
18483            panic!("expected completion menu to be open");
18484        }
18485    });
18486}
18487
18488fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
18489    let entries = menu.entries.borrow();
18490    entries.iter().map(|mat| mat.string.clone()).collect()
18491}
18492
18493#[gpui::test]
18494async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
18495    init_test(cx, |settings| {
18496        settings.defaults.formatter = Some(FormatterList::Single(Formatter::Prettier))
18497    });
18498
18499    let fs = FakeFs::new(cx.executor());
18500    fs.insert_file(path!("/file.ts"), Default::default()).await;
18501
18502    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
18503    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18504
18505    language_registry.add(Arc::new(Language::new(
18506        LanguageConfig {
18507            name: "TypeScript".into(),
18508            matcher: LanguageMatcher {
18509                path_suffixes: vec!["ts".to_string()],
18510                ..Default::default()
18511            },
18512            ..Default::default()
18513        },
18514        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
18515    )));
18516    update_test_language_settings(cx, |settings| {
18517        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
18518    });
18519
18520    let test_plugin = "test_plugin";
18521    let _ = language_registry.register_fake_lsp(
18522        "TypeScript",
18523        FakeLspAdapter {
18524            prettier_plugins: vec![test_plugin],
18525            ..Default::default()
18526        },
18527    );
18528
18529    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
18530    let buffer = project
18531        .update(cx, |project, cx| {
18532            project.open_local_buffer(path!("/file.ts"), cx)
18533        })
18534        .await
18535        .unwrap();
18536
18537    let buffer_text = "one\ntwo\nthree\n";
18538    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
18539    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
18540    editor.update_in(cx, |editor, window, cx| {
18541        editor.set_text(buffer_text, window, cx)
18542    });
18543
18544    editor
18545        .update_in(cx, |editor, window, cx| {
18546            editor.perform_format(
18547                project.clone(),
18548                FormatTrigger::Manual,
18549                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18550                window,
18551                cx,
18552            )
18553        })
18554        .unwrap()
18555        .await;
18556    assert_eq!(
18557        editor.update(cx, |editor, cx| editor.text(cx)),
18558        buffer_text.to_string() + prettier_format_suffix,
18559        "Test prettier formatting was not applied to the original buffer text",
18560    );
18561
18562    update_test_language_settings(cx, |settings| {
18563        settings.defaults.formatter = Some(FormatterList::default())
18564    });
18565    let format = editor.update_in(cx, |editor, window, cx| {
18566        editor.perform_format(
18567            project.clone(),
18568            FormatTrigger::Manual,
18569            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18570            window,
18571            cx,
18572        )
18573    });
18574    format.await.unwrap();
18575    assert_eq!(
18576        editor.update(cx, |editor, cx| editor.text(cx)),
18577        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
18578        "Autoformatting (via test prettier) was not applied to the original buffer text",
18579    );
18580}
18581
18582#[gpui::test]
18583async fn test_addition_reverts(cx: &mut TestAppContext) {
18584    init_test(cx, |_| {});
18585    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18586    let base_text = indoc! {r#"
18587        struct Row;
18588        struct Row1;
18589        struct Row2;
18590
18591        struct Row4;
18592        struct Row5;
18593        struct Row6;
18594
18595        struct Row8;
18596        struct Row9;
18597        struct Row10;"#};
18598
18599    // When addition hunks are not adjacent to carets, no hunk revert is performed
18600    assert_hunk_revert(
18601        indoc! {r#"struct Row;
18602                   struct Row1;
18603                   struct Row1.1;
18604                   struct Row1.2;
18605                   struct Row2;ˇ
18606
18607                   struct Row4;
18608                   struct Row5;
18609                   struct Row6;
18610
18611                   struct Row8;
18612                   ˇstruct Row9;
18613                   struct Row9.1;
18614                   struct Row9.2;
18615                   struct Row9.3;
18616                   struct Row10;"#},
18617        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18618        indoc! {r#"struct Row;
18619                   struct Row1;
18620                   struct Row1.1;
18621                   struct Row1.2;
18622                   struct Row2;ˇ
18623
18624                   struct Row4;
18625                   struct Row5;
18626                   struct Row6;
18627
18628                   struct Row8;
18629                   ˇstruct Row9;
18630                   struct Row9.1;
18631                   struct Row9.2;
18632                   struct Row9.3;
18633                   struct Row10;"#},
18634        base_text,
18635        &mut cx,
18636    );
18637    // Same for selections
18638    assert_hunk_revert(
18639        indoc! {r#"struct Row;
18640                   struct Row1;
18641                   struct Row2;
18642                   struct Row2.1;
18643                   struct Row2.2;
18644                   «ˇ
18645                   struct Row4;
18646                   struct» Row5;
18647                   «struct Row6;
18648                   ˇ»
18649                   struct Row9.1;
18650                   struct Row9.2;
18651                   struct Row9.3;
18652                   struct Row8;
18653                   struct Row9;
18654                   struct Row10;"#},
18655        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18656        indoc! {r#"struct Row;
18657                   struct Row1;
18658                   struct Row2;
18659                   struct Row2.1;
18660                   struct Row2.2;
18661                   «ˇ
18662                   struct Row4;
18663                   struct» Row5;
18664                   «struct Row6;
18665                   ˇ»
18666                   struct Row9.1;
18667                   struct Row9.2;
18668                   struct Row9.3;
18669                   struct Row8;
18670                   struct Row9;
18671                   struct Row10;"#},
18672        base_text,
18673        &mut cx,
18674    );
18675
18676    // When carets and selections intersect the addition hunks, those are reverted.
18677    // Adjacent carets got merged.
18678    assert_hunk_revert(
18679        indoc! {r#"struct Row;
18680                   ˇ// something on the top
18681                   struct Row1;
18682                   struct Row2;
18683                   struct Roˇw3.1;
18684                   struct Row2.2;
18685                   struct Row2.3;ˇ
18686
18687                   struct Row4;
18688                   struct ˇRow5.1;
18689                   struct Row5.2;
18690                   struct «Rowˇ»5.3;
18691                   struct Row5;
18692                   struct Row6;
18693                   ˇ
18694                   struct Row9.1;
18695                   struct «Rowˇ»9.2;
18696                   struct «ˇRow»9.3;
18697                   struct Row8;
18698                   struct Row9;
18699                   «ˇ// something on bottom»
18700                   struct Row10;"#},
18701        vec![
18702            DiffHunkStatusKind::Added,
18703            DiffHunkStatusKind::Added,
18704            DiffHunkStatusKind::Added,
18705            DiffHunkStatusKind::Added,
18706            DiffHunkStatusKind::Added,
18707        ],
18708        indoc! {r#"struct Row;
18709                   ˇstruct Row1;
18710                   struct Row2;
18711                   ˇ
18712                   struct Row4;
18713                   ˇstruct Row5;
18714                   struct Row6;
18715                   ˇ
18716                   ˇstruct Row8;
18717                   struct Row9;
18718                   ˇstruct Row10;"#},
18719        base_text,
18720        &mut cx,
18721    );
18722}
18723
18724#[gpui::test]
18725async fn test_modification_reverts(cx: &mut TestAppContext) {
18726    init_test(cx, |_| {});
18727    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18728    let base_text = indoc! {r#"
18729        struct Row;
18730        struct Row1;
18731        struct Row2;
18732
18733        struct Row4;
18734        struct Row5;
18735        struct Row6;
18736
18737        struct Row8;
18738        struct Row9;
18739        struct Row10;"#};
18740
18741    // Modification hunks behave the same as the addition ones.
18742    assert_hunk_revert(
18743        indoc! {r#"struct Row;
18744                   struct Row1;
18745                   struct Row33;
18746                   ˇ
18747                   struct Row4;
18748                   struct Row5;
18749                   struct Row6;
18750                   ˇ
18751                   struct Row99;
18752                   struct Row9;
18753                   struct Row10;"#},
18754        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18755        indoc! {r#"struct Row;
18756                   struct Row1;
18757                   struct Row33;
18758                   ˇ
18759                   struct Row4;
18760                   struct Row5;
18761                   struct Row6;
18762                   ˇ
18763                   struct Row99;
18764                   struct Row9;
18765                   struct Row10;"#},
18766        base_text,
18767        &mut cx,
18768    );
18769    assert_hunk_revert(
18770        indoc! {r#"struct Row;
18771                   struct Row1;
18772                   struct Row33;
18773                   «ˇ
18774                   struct Row4;
18775                   struct» Row5;
18776                   «struct Row6;
18777                   ˇ»
18778                   struct Row99;
18779                   struct Row9;
18780                   struct Row10;"#},
18781        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18782        indoc! {r#"struct Row;
18783                   struct Row1;
18784                   struct Row33;
18785                   «ˇ
18786                   struct Row4;
18787                   struct» Row5;
18788                   «struct Row6;
18789                   ˇ»
18790                   struct Row99;
18791                   struct Row9;
18792                   struct Row10;"#},
18793        base_text,
18794        &mut cx,
18795    );
18796
18797    assert_hunk_revert(
18798        indoc! {r#"ˇstruct Row1.1;
18799                   struct Row1;
18800                   «ˇstr»uct Row22;
18801
18802                   struct ˇRow44;
18803                   struct Row5;
18804                   struct «Rˇ»ow66;ˇ
18805
18806                   «struˇ»ct Row88;
18807                   struct Row9;
18808                   struct Row1011;ˇ"#},
18809        vec![
18810            DiffHunkStatusKind::Modified,
18811            DiffHunkStatusKind::Modified,
18812            DiffHunkStatusKind::Modified,
18813            DiffHunkStatusKind::Modified,
18814            DiffHunkStatusKind::Modified,
18815            DiffHunkStatusKind::Modified,
18816        ],
18817        indoc! {r#"struct Row;
18818                   ˇstruct Row1;
18819                   struct Row2;
18820                   ˇ
18821                   struct Row4;
18822                   ˇstruct Row5;
18823                   struct Row6;
18824                   ˇ
18825                   struct Row8;
18826                   ˇstruct Row9;
18827                   struct Row10;ˇ"#},
18828        base_text,
18829        &mut cx,
18830    );
18831}
18832
18833#[gpui::test]
18834async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
18835    init_test(cx, |_| {});
18836    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18837    let base_text = indoc! {r#"
18838        one
18839
18840        two
18841        three
18842        "#};
18843
18844    cx.set_head_text(base_text);
18845    cx.set_state("\nˇ\n");
18846    cx.executor().run_until_parked();
18847    cx.update_editor(|editor, _window, cx| {
18848        editor.expand_selected_diff_hunks(cx);
18849    });
18850    cx.executor().run_until_parked();
18851    cx.update_editor(|editor, window, cx| {
18852        editor.backspace(&Default::default(), window, cx);
18853    });
18854    cx.run_until_parked();
18855    cx.assert_state_with_diff(
18856        indoc! {r#"
18857
18858        - two
18859        - threeˇ
18860        +
18861        "#}
18862        .to_string(),
18863    );
18864}
18865
18866#[gpui::test]
18867async fn test_deletion_reverts(cx: &mut TestAppContext) {
18868    init_test(cx, |_| {});
18869    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18870    let base_text = indoc! {r#"struct Row;
18871struct Row1;
18872struct Row2;
18873
18874struct Row4;
18875struct Row5;
18876struct Row6;
18877
18878struct Row8;
18879struct Row9;
18880struct Row10;"#};
18881
18882    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
18883    assert_hunk_revert(
18884        indoc! {r#"struct Row;
18885                   struct Row2;
18886
18887                   ˇstruct Row4;
18888                   struct Row5;
18889                   struct Row6;
18890                   ˇ
18891                   struct Row8;
18892                   struct Row10;"#},
18893        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18894        indoc! {r#"struct Row;
18895                   struct Row2;
18896
18897                   ˇstruct Row4;
18898                   struct Row5;
18899                   struct Row6;
18900                   ˇ
18901                   struct Row8;
18902                   struct Row10;"#},
18903        base_text,
18904        &mut cx,
18905    );
18906    assert_hunk_revert(
18907        indoc! {r#"struct Row;
18908                   struct Row2;
18909
18910                   «ˇstruct Row4;
18911                   struct» Row5;
18912                   «struct Row6;
18913                   ˇ»
18914                   struct Row8;
18915                   struct Row10;"#},
18916        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18917        indoc! {r#"struct Row;
18918                   struct Row2;
18919
18920                   «ˇstruct Row4;
18921                   struct» Row5;
18922                   «struct Row6;
18923                   ˇ»
18924                   struct Row8;
18925                   struct Row10;"#},
18926        base_text,
18927        &mut cx,
18928    );
18929
18930    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
18931    assert_hunk_revert(
18932        indoc! {r#"struct Row;
18933                   ˇstruct Row2;
18934
18935                   struct Row4;
18936                   struct Row5;
18937                   struct Row6;
18938
18939                   struct Row8;ˇ
18940                   struct Row10;"#},
18941        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18942        indoc! {r#"struct Row;
18943                   struct Row1;
18944                   ˇstruct Row2;
18945
18946                   struct Row4;
18947                   struct Row5;
18948                   struct Row6;
18949
18950                   struct Row8;ˇ
18951                   struct Row9;
18952                   struct Row10;"#},
18953        base_text,
18954        &mut cx,
18955    );
18956    assert_hunk_revert(
18957        indoc! {r#"struct Row;
18958                   struct Row2«ˇ;
18959                   struct Row4;
18960                   struct» Row5;
18961                   «struct Row6;
18962
18963                   struct Row8;ˇ»
18964                   struct Row10;"#},
18965        vec![
18966            DiffHunkStatusKind::Deleted,
18967            DiffHunkStatusKind::Deleted,
18968            DiffHunkStatusKind::Deleted,
18969        ],
18970        indoc! {r#"struct Row;
18971                   struct Row1;
18972                   struct Row2«ˇ;
18973
18974                   struct Row4;
18975                   struct» Row5;
18976                   «struct Row6;
18977
18978                   struct Row8;ˇ»
18979                   struct Row9;
18980                   struct Row10;"#},
18981        base_text,
18982        &mut cx,
18983    );
18984}
18985
18986#[gpui::test]
18987async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
18988    init_test(cx, |_| {});
18989
18990    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
18991    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
18992    let base_text_3 =
18993        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
18994
18995    let text_1 = edit_first_char_of_every_line(base_text_1);
18996    let text_2 = edit_first_char_of_every_line(base_text_2);
18997    let text_3 = edit_first_char_of_every_line(base_text_3);
18998
18999    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
19000    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
19001    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
19002
19003    let multibuffer = cx.new(|cx| {
19004        let mut multibuffer = MultiBuffer::new(ReadWrite);
19005        multibuffer.push_excerpts(
19006            buffer_1.clone(),
19007            [
19008                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19009                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19010                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19011            ],
19012            cx,
19013        );
19014        multibuffer.push_excerpts(
19015            buffer_2.clone(),
19016            [
19017                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19018                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19019                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19020            ],
19021            cx,
19022        );
19023        multibuffer.push_excerpts(
19024            buffer_3.clone(),
19025            [
19026                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19027                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19028                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19029            ],
19030            cx,
19031        );
19032        multibuffer
19033    });
19034
19035    let fs = FakeFs::new(cx.executor());
19036    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
19037    let (editor, cx) = cx
19038        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
19039    editor.update_in(cx, |editor, _window, cx| {
19040        for (buffer, diff_base) in [
19041            (buffer_1.clone(), base_text_1),
19042            (buffer_2.clone(), base_text_2),
19043            (buffer_3.clone(), base_text_3),
19044        ] {
19045            let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
19046            editor
19047                .buffer
19048                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
19049        }
19050    });
19051    cx.executor().run_until_parked();
19052
19053    editor.update_in(cx, |editor, window, cx| {
19054        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}");
19055        editor.select_all(&SelectAll, window, cx);
19056        editor.git_restore(&Default::default(), window, cx);
19057    });
19058    cx.executor().run_until_parked();
19059
19060    // When all ranges are selected, all buffer hunks are reverted.
19061    editor.update(cx, |editor, cx| {
19062        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");
19063    });
19064    buffer_1.update(cx, |buffer, _| {
19065        assert_eq!(buffer.text(), base_text_1);
19066    });
19067    buffer_2.update(cx, |buffer, _| {
19068        assert_eq!(buffer.text(), base_text_2);
19069    });
19070    buffer_3.update(cx, |buffer, _| {
19071        assert_eq!(buffer.text(), base_text_3);
19072    });
19073
19074    editor.update_in(cx, |editor, window, cx| {
19075        editor.undo(&Default::default(), window, cx);
19076    });
19077
19078    editor.update_in(cx, |editor, window, cx| {
19079        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19080            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
19081        });
19082        editor.git_restore(&Default::default(), window, cx);
19083    });
19084
19085    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
19086    // but not affect buffer_2 and its related excerpts.
19087    editor.update(cx, |editor, cx| {
19088        assert_eq!(
19089            editor.text(cx),
19090            "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}"
19091        );
19092    });
19093    buffer_1.update(cx, |buffer, _| {
19094        assert_eq!(buffer.text(), base_text_1);
19095    });
19096    buffer_2.update(cx, |buffer, _| {
19097        assert_eq!(
19098            buffer.text(),
19099            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
19100        );
19101    });
19102    buffer_3.update(cx, |buffer, _| {
19103        assert_eq!(
19104            buffer.text(),
19105            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
19106        );
19107    });
19108
19109    fn edit_first_char_of_every_line(text: &str) -> String {
19110        text.split('\n')
19111            .map(|line| format!("X{}", &line[1..]))
19112            .collect::<Vec<_>>()
19113            .join("\n")
19114    }
19115}
19116
19117#[gpui::test]
19118async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
19119    init_test(cx, |_| {});
19120
19121    let cols = 4;
19122    let rows = 10;
19123    let sample_text_1 = sample_text(rows, cols, 'a');
19124    assert_eq!(
19125        sample_text_1,
19126        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
19127    );
19128    let sample_text_2 = sample_text(rows, cols, 'l');
19129    assert_eq!(
19130        sample_text_2,
19131        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
19132    );
19133    let sample_text_3 = sample_text(rows, cols, 'v');
19134    assert_eq!(
19135        sample_text_3,
19136        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
19137    );
19138
19139    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
19140    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
19141    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
19142
19143    let multi_buffer = cx.new(|cx| {
19144        let mut multibuffer = MultiBuffer::new(ReadWrite);
19145        multibuffer.push_excerpts(
19146            buffer_1.clone(),
19147            [
19148                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19149                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19150                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19151            ],
19152            cx,
19153        );
19154        multibuffer.push_excerpts(
19155            buffer_2.clone(),
19156            [
19157                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19158                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19159                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19160            ],
19161            cx,
19162        );
19163        multibuffer.push_excerpts(
19164            buffer_3.clone(),
19165            [
19166                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19167                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19168                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19169            ],
19170            cx,
19171        );
19172        multibuffer
19173    });
19174
19175    let fs = FakeFs::new(cx.executor());
19176    fs.insert_tree(
19177        "/a",
19178        json!({
19179            "main.rs": sample_text_1,
19180            "other.rs": sample_text_2,
19181            "lib.rs": sample_text_3,
19182        }),
19183    )
19184    .await;
19185    let project = Project::test(fs, ["/a".as_ref()], cx).await;
19186    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19187    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19188    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19189        Editor::new(
19190            EditorMode::full(),
19191            multi_buffer,
19192            Some(project.clone()),
19193            window,
19194            cx,
19195        )
19196    });
19197    let multibuffer_item_id = workspace
19198        .update(cx, |workspace, window, cx| {
19199            assert!(
19200                workspace.active_item(cx).is_none(),
19201                "active item should be None before the first item is added"
19202            );
19203            workspace.add_item_to_active_pane(
19204                Box::new(multi_buffer_editor.clone()),
19205                None,
19206                true,
19207                window,
19208                cx,
19209            );
19210            let active_item = workspace
19211                .active_item(cx)
19212                .expect("should have an active item after adding the multi buffer");
19213            assert_eq!(
19214                active_item.buffer_kind(cx),
19215                ItemBufferKind::Multibuffer,
19216                "A multi buffer was expected to active after adding"
19217            );
19218            active_item.item_id()
19219        })
19220        .unwrap();
19221    cx.executor().run_until_parked();
19222
19223    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19224        editor.change_selections(
19225            SelectionEffects::scroll(Autoscroll::Next),
19226            window,
19227            cx,
19228            |s| s.select_ranges(Some(1..2)),
19229        );
19230        editor.open_excerpts(&OpenExcerpts, window, cx);
19231    });
19232    cx.executor().run_until_parked();
19233    let first_item_id = workspace
19234        .update(cx, |workspace, window, cx| {
19235            let active_item = workspace
19236                .active_item(cx)
19237                .expect("should have an active item after navigating into the 1st buffer");
19238            let first_item_id = active_item.item_id();
19239            assert_ne!(
19240                first_item_id, multibuffer_item_id,
19241                "Should navigate into the 1st buffer and activate it"
19242            );
19243            assert_eq!(
19244                active_item.buffer_kind(cx),
19245                ItemBufferKind::Singleton,
19246                "New active item should be a singleton buffer"
19247            );
19248            assert_eq!(
19249                active_item
19250                    .act_as::<Editor>(cx)
19251                    .expect("should have navigated into an editor for the 1st buffer")
19252                    .read(cx)
19253                    .text(cx),
19254                sample_text_1
19255            );
19256
19257            workspace
19258                .go_back(workspace.active_pane().downgrade(), window, cx)
19259                .detach_and_log_err(cx);
19260
19261            first_item_id
19262        })
19263        .unwrap();
19264    cx.executor().run_until_parked();
19265    workspace
19266        .update(cx, |workspace, _, cx| {
19267            let active_item = workspace
19268                .active_item(cx)
19269                .expect("should have an active item after navigating back");
19270            assert_eq!(
19271                active_item.item_id(),
19272                multibuffer_item_id,
19273                "Should navigate back to the multi buffer"
19274            );
19275            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19276        })
19277        .unwrap();
19278
19279    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19280        editor.change_selections(
19281            SelectionEffects::scroll(Autoscroll::Next),
19282            window,
19283            cx,
19284            |s| s.select_ranges(Some(39..40)),
19285        );
19286        editor.open_excerpts(&OpenExcerpts, window, cx);
19287    });
19288    cx.executor().run_until_parked();
19289    let second_item_id = workspace
19290        .update(cx, |workspace, window, cx| {
19291            let active_item = workspace
19292                .active_item(cx)
19293                .expect("should have an active item after navigating into the 2nd buffer");
19294            let second_item_id = active_item.item_id();
19295            assert_ne!(
19296                second_item_id, multibuffer_item_id,
19297                "Should navigate away from the multibuffer"
19298            );
19299            assert_ne!(
19300                second_item_id, first_item_id,
19301                "Should navigate into the 2nd buffer and activate it"
19302            );
19303            assert_eq!(
19304                active_item.buffer_kind(cx),
19305                ItemBufferKind::Singleton,
19306                "New active item should be a singleton buffer"
19307            );
19308            assert_eq!(
19309                active_item
19310                    .act_as::<Editor>(cx)
19311                    .expect("should have navigated into an editor")
19312                    .read(cx)
19313                    .text(cx),
19314                sample_text_2
19315            );
19316
19317            workspace
19318                .go_back(workspace.active_pane().downgrade(), window, cx)
19319                .detach_and_log_err(cx);
19320
19321            second_item_id
19322        })
19323        .unwrap();
19324    cx.executor().run_until_parked();
19325    workspace
19326        .update(cx, |workspace, _, cx| {
19327            let active_item = workspace
19328                .active_item(cx)
19329                .expect("should have an active item after navigating back from the 2nd buffer");
19330            assert_eq!(
19331                active_item.item_id(),
19332                multibuffer_item_id,
19333                "Should navigate back from the 2nd buffer to the multi buffer"
19334            );
19335            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19336        })
19337        .unwrap();
19338
19339    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19340        editor.change_selections(
19341            SelectionEffects::scroll(Autoscroll::Next),
19342            window,
19343            cx,
19344            |s| s.select_ranges(Some(70..70)),
19345        );
19346        editor.open_excerpts(&OpenExcerpts, window, cx);
19347    });
19348    cx.executor().run_until_parked();
19349    workspace
19350        .update(cx, |workspace, window, cx| {
19351            let active_item = workspace
19352                .active_item(cx)
19353                .expect("should have an active item after navigating into the 3rd buffer");
19354            let third_item_id = active_item.item_id();
19355            assert_ne!(
19356                third_item_id, multibuffer_item_id,
19357                "Should navigate into the 3rd buffer and activate it"
19358            );
19359            assert_ne!(third_item_id, first_item_id);
19360            assert_ne!(third_item_id, second_item_id);
19361            assert_eq!(
19362                active_item.buffer_kind(cx),
19363                ItemBufferKind::Singleton,
19364                "New active item should be a singleton buffer"
19365            );
19366            assert_eq!(
19367                active_item
19368                    .act_as::<Editor>(cx)
19369                    .expect("should have navigated into an editor")
19370                    .read(cx)
19371                    .text(cx),
19372                sample_text_3
19373            );
19374
19375            workspace
19376                .go_back(workspace.active_pane().downgrade(), window, cx)
19377                .detach_and_log_err(cx);
19378        })
19379        .unwrap();
19380    cx.executor().run_until_parked();
19381    workspace
19382        .update(cx, |workspace, _, cx| {
19383            let active_item = workspace
19384                .active_item(cx)
19385                .expect("should have an active item after navigating back from the 3rd buffer");
19386            assert_eq!(
19387                active_item.item_id(),
19388                multibuffer_item_id,
19389                "Should navigate back from the 3rd buffer to the multi buffer"
19390            );
19391            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19392        })
19393        .unwrap();
19394}
19395
19396#[gpui::test]
19397async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19398    init_test(cx, |_| {});
19399
19400    let mut cx = EditorTestContext::new(cx).await;
19401
19402    let diff_base = r#"
19403        use some::mod;
19404
19405        const A: u32 = 42;
19406
19407        fn main() {
19408            println!("hello");
19409
19410            println!("world");
19411        }
19412        "#
19413    .unindent();
19414
19415    cx.set_state(
19416        &r#"
19417        use some::modified;
19418
19419        ˇ
19420        fn main() {
19421            println!("hello there");
19422
19423            println!("around the");
19424            println!("world");
19425        }
19426        "#
19427        .unindent(),
19428    );
19429
19430    cx.set_head_text(&diff_base);
19431    executor.run_until_parked();
19432
19433    cx.update_editor(|editor, window, cx| {
19434        editor.go_to_next_hunk(&GoToHunk, window, cx);
19435        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19436    });
19437    executor.run_until_parked();
19438    cx.assert_state_with_diff(
19439        r#"
19440          use some::modified;
19441
19442
19443          fn main() {
19444        -     println!("hello");
19445        + ˇ    println!("hello there");
19446
19447              println!("around the");
19448              println!("world");
19449          }
19450        "#
19451        .unindent(),
19452    );
19453
19454    cx.update_editor(|editor, window, cx| {
19455        for _ in 0..2 {
19456            editor.go_to_next_hunk(&GoToHunk, window, cx);
19457            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19458        }
19459    });
19460    executor.run_until_parked();
19461    cx.assert_state_with_diff(
19462        r#"
19463        - use some::mod;
19464        + ˇuse some::modified;
19465
19466
19467          fn main() {
19468        -     println!("hello");
19469        +     println!("hello there");
19470
19471        +     println!("around the");
19472              println!("world");
19473          }
19474        "#
19475        .unindent(),
19476    );
19477
19478    cx.update_editor(|editor, window, cx| {
19479        editor.go_to_next_hunk(&GoToHunk, window, cx);
19480        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19481    });
19482    executor.run_until_parked();
19483    cx.assert_state_with_diff(
19484        r#"
19485        - use some::mod;
19486        + use some::modified;
19487
19488        - const A: u32 = 42;
19489          ˇ
19490          fn main() {
19491        -     println!("hello");
19492        +     println!("hello there");
19493
19494        +     println!("around the");
19495              println!("world");
19496          }
19497        "#
19498        .unindent(),
19499    );
19500
19501    cx.update_editor(|editor, window, cx| {
19502        editor.cancel(&Cancel, window, cx);
19503    });
19504
19505    cx.assert_state_with_diff(
19506        r#"
19507          use some::modified;
19508
19509          ˇ
19510          fn main() {
19511              println!("hello there");
19512
19513              println!("around the");
19514              println!("world");
19515          }
19516        "#
19517        .unindent(),
19518    );
19519}
19520
19521#[gpui::test]
19522async fn test_diff_base_change_with_expanded_diff_hunks(
19523    executor: BackgroundExecutor,
19524    cx: &mut TestAppContext,
19525) {
19526    init_test(cx, |_| {});
19527
19528    let mut cx = EditorTestContext::new(cx).await;
19529
19530    let diff_base = r#"
19531        use some::mod1;
19532        use some::mod2;
19533
19534        const A: u32 = 42;
19535        const B: u32 = 42;
19536        const C: u32 = 42;
19537
19538        fn main() {
19539            println!("hello");
19540
19541            println!("world");
19542        }
19543        "#
19544    .unindent();
19545
19546    cx.set_state(
19547        &r#"
19548        use some::mod2;
19549
19550        const A: u32 = 42;
19551        const C: u32 = 42;
19552
19553        fn main(ˇ) {
19554            //println!("hello");
19555
19556            println!("world");
19557            //
19558            //
19559        }
19560        "#
19561        .unindent(),
19562    );
19563
19564    cx.set_head_text(&diff_base);
19565    executor.run_until_parked();
19566
19567    cx.update_editor(|editor, window, cx| {
19568        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19569    });
19570    executor.run_until_parked();
19571    cx.assert_state_with_diff(
19572        r#"
19573        - use some::mod1;
19574          use some::mod2;
19575
19576          const A: u32 = 42;
19577        - const B: u32 = 42;
19578          const C: u32 = 42;
19579
19580          fn main(ˇ) {
19581        -     println!("hello");
19582        +     //println!("hello");
19583
19584              println!("world");
19585        +     //
19586        +     //
19587          }
19588        "#
19589        .unindent(),
19590    );
19591
19592    cx.set_head_text("new diff base!");
19593    executor.run_until_parked();
19594    cx.assert_state_with_diff(
19595        r#"
19596        - new diff base!
19597        + use some::mod2;
19598        +
19599        + const A: u32 = 42;
19600        + const C: u32 = 42;
19601        +
19602        + fn main(ˇ) {
19603        +     //println!("hello");
19604        +
19605        +     println!("world");
19606        +     //
19607        +     //
19608        + }
19609        "#
19610        .unindent(),
19611    );
19612}
19613
19614#[gpui::test]
19615async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
19616    init_test(cx, |_| {});
19617
19618    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19619    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19620    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19621    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19622    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
19623    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
19624
19625    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
19626    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
19627    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
19628
19629    let multi_buffer = cx.new(|cx| {
19630        let mut multibuffer = MultiBuffer::new(ReadWrite);
19631        multibuffer.push_excerpts(
19632            buffer_1.clone(),
19633            [
19634                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19635                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19636                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19637            ],
19638            cx,
19639        );
19640        multibuffer.push_excerpts(
19641            buffer_2.clone(),
19642            [
19643                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19644                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19645                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19646            ],
19647            cx,
19648        );
19649        multibuffer.push_excerpts(
19650            buffer_3.clone(),
19651            [
19652                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19653                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19654                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19655            ],
19656            cx,
19657        );
19658        multibuffer
19659    });
19660
19661    let editor =
19662        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19663    editor
19664        .update(cx, |editor, _window, cx| {
19665            for (buffer, diff_base) in [
19666                (buffer_1.clone(), file_1_old),
19667                (buffer_2.clone(), file_2_old),
19668                (buffer_3.clone(), file_3_old),
19669            ] {
19670                let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
19671                editor
19672                    .buffer
19673                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
19674            }
19675        })
19676        .unwrap();
19677
19678    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19679    cx.run_until_parked();
19680
19681    cx.assert_editor_state(
19682        &"
19683            ˇaaa
19684            ccc
19685            ddd
19686
19687            ggg
19688            hhh
19689
19690
19691            lll
19692            mmm
19693            NNN
19694
19695            qqq
19696            rrr
19697
19698            uuu
19699            111
19700            222
19701            333
19702
19703            666
19704            777
19705
19706            000
19707            !!!"
19708        .unindent(),
19709    );
19710
19711    cx.update_editor(|editor, window, cx| {
19712        editor.select_all(&SelectAll, window, cx);
19713        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19714    });
19715    cx.executor().run_until_parked();
19716
19717    cx.assert_state_with_diff(
19718        "
19719            «aaa
19720          - bbb
19721            ccc
19722            ddd
19723
19724            ggg
19725            hhh
19726
19727
19728            lll
19729            mmm
19730          - nnn
19731          + NNN
19732
19733            qqq
19734            rrr
19735
19736            uuu
19737            111
19738            222
19739            333
19740
19741          + 666
19742            777
19743
19744            000
19745            !!!ˇ»"
19746            .unindent(),
19747    );
19748}
19749
19750#[gpui::test]
19751async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
19752    init_test(cx, |_| {});
19753
19754    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
19755    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
19756
19757    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
19758    let multi_buffer = cx.new(|cx| {
19759        let mut multibuffer = MultiBuffer::new(ReadWrite);
19760        multibuffer.push_excerpts(
19761            buffer.clone(),
19762            [
19763                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
19764                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
19765                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
19766            ],
19767            cx,
19768        );
19769        multibuffer
19770    });
19771
19772    let editor =
19773        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19774    editor
19775        .update(cx, |editor, _window, cx| {
19776            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
19777            editor
19778                .buffer
19779                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
19780        })
19781        .unwrap();
19782
19783    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19784    cx.run_until_parked();
19785
19786    cx.update_editor(|editor, window, cx| {
19787        editor.expand_all_diff_hunks(&Default::default(), window, cx)
19788    });
19789    cx.executor().run_until_parked();
19790
19791    // When the start of a hunk coincides with the start of its excerpt,
19792    // the hunk is expanded. When the start of a hunk is earlier than
19793    // the start of its excerpt, the hunk is not expanded.
19794    cx.assert_state_with_diff(
19795        "
19796            ˇaaa
19797          - bbb
19798          + BBB
19799
19800          - ddd
19801          - eee
19802          + DDD
19803          + EEE
19804            fff
19805
19806            iii
19807        "
19808        .unindent(),
19809    );
19810}
19811
19812#[gpui::test]
19813async fn test_edits_around_expanded_insertion_hunks(
19814    executor: BackgroundExecutor,
19815    cx: &mut TestAppContext,
19816) {
19817    init_test(cx, |_| {});
19818
19819    let mut cx = EditorTestContext::new(cx).await;
19820
19821    let diff_base = r#"
19822        use some::mod1;
19823        use some::mod2;
19824
19825        const A: u32 = 42;
19826
19827        fn main() {
19828            println!("hello");
19829
19830            println!("world");
19831        }
19832        "#
19833    .unindent();
19834    executor.run_until_parked();
19835    cx.set_state(
19836        &r#"
19837        use some::mod1;
19838        use some::mod2;
19839
19840        const A: u32 = 42;
19841        const B: u32 = 42;
19842        const C: u32 = 42;
19843        ˇ
19844
19845        fn main() {
19846            println!("hello");
19847
19848            println!("world");
19849        }
19850        "#
19851        .unindent(),
19852    );
19853
19854    cx.set_head_text(&diff_base);
19855    executor.run_until_parked();
19856
19857    cx.update_editor(|editor, window, cx| {
19858        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19859    });
19860    executor.run_until_parked();
19861
19862    cx.assert_state_with_diff(
19863        r#"
19864        use some::mod1;
19865        use some::mod2;
19866
19867        const A: u32 = 42;
19868      + const B: u32 = 42;
19869      + const C: u32 = 42;
19870      + ˇ
19871
19872        fn main() {
19873            println!("hello");
19874
19875            println!("world");
19876        }
19877      "#
19878        .unindent(),
19879    );
19880
19881    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
19882    executor.run_until_parked();
19883
19884    cx.assert_state_with_diff(
19885        r#"
19886        use some::mod1;
19887        use some::mod2;
19888
19889        const A: u32 = 42;
19890      + const B: u32 = 42;
19891      + const C: u32 = 42;
19892      + const D: u32 = 42;
19893      + ˇ
19894
19895        fn main() {
19896            println!("hello");
19897
19898            println!("world");
19899        }
19900      "#
19901        .unindent(),
19902    );
19903
19904    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
19905    executor.run_until_parked();
19906
19907    cx.assert_state_with_diff(
19908        r#"
19909        use some::mod1;
19910        use some::mod2;
19911
19912        const A: u32 = 42;
19913      + const B: u32 = 42;
19914      + const C: u32 = 42;
19915      + const D: u32 = 42;
19916      + const E: u32 = 42;
19917      + ˇ
19918
19919        fn main() {
19920            println!("hello");
19921
19922            println!("world");
19923        }
19924      "#
19925        .unindent(),
19926    );
19927
19928    cx.update_editor(|editor, window, cx| {
19929        editor.delete_line(&DeleteLine, window, cx);
19930    });
19931    executor.run_until_parked();
19932
19933    cx.assert_state_with_diff(
19934        r#"
19935        use some::mod1;
19936        use some::mod2;
19937
19938        const A: u32 = 42;
19939      + const B: u32 = 42;
19940      + const C: u32 = 42;
19941      + const D: u32 = 42;
19942      + const E: u32 = 42;
19943        ˇ
19944        fn main() {
19945            println!("hello");
19946
19947            println!("world");
19948        }
19949      "#
19950        .unindent(),
19951    );
19952
19953    cx.update_editor(|editor, window, cx| {
19954        editor.move_up(&MoveUp, window, cx);
19955        editor.delete_line(&DeleteLine, window, cx);
19956        editor.move_up(&MoveUp, window, cx);
19957        editor.delete_line(&DeleteLine, window, cx);
19958        editor.move_up(&MoveUp, window, cx);
19959        editor.delete_line(&DeleteLine, window, cx);
19960    });
19961    executor.run_until_parked();
19962    cx.assert_state_with_diff(
19963        r#"
19964        use some::mod1;
19965        use some::mod2;
19966
19967        const A: u32 = 42;
19968      + const B: u32 = 42;
19969        ˇ
19970        fn main() {
19971            println!("hello");
19972
19973            println!("world");
19974        }
19975      "#
19976        .unindent(),
19977    );
19978
19979    cx.update_editor(|editor, window, cx| {
19980        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
19981        editor.delete_line(&DeleteLine, window, cx);
19982    });
19983    executor.run_until_parked();
19984    cx.assert_state_with_diff(
19985        r#"
19986        ˇ
19987        fn main() {
19988            println!("hello");
19989
19990            println!("world");
19991        }
19992      "#
19993        .unindent(),
19994    );
19995}
19996
19997#[gpui::test]
19998async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
19999    init_test(cx, |_| {});
20000
20001    let mut cx = EditorTestContext::new(cx).await;
20002    cx.set_head_text(indoc! { "
20003        one
20004        two
20005        three
20006        four
20007        five
20008        "
20009    });
20010    cx.set_state(indoc! { "
20011        one
20012        ˇthree
20013        five
20014    "});
20015    cx.run_until_parked();
20016    cx.update_editor(|editor, window, cx| {
20017        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20018    });
20019    cx.assert_state_with_diff(
20020        indoc! { "
20021        one
20022      - two
20023        ˇthree
20024      - four
20025        five
20026    "}
20027        .to_string(),
20028    );
20029    cx.update_editor(|editor, window, cx| {
20030        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20031    });
20032
20033    cx.assert_state_with_diff(
20034        indoc! { "
20035        one
20036        ˇthree
20037        five
20038    "}
20039        .to_string(),
20040    );
20041
20042    cx.set_state(indoc! { "
20043        one
20044        ˇTWO
20045        three
20046        four
20047        five
20048    "});
20049    cx.run_until_parked();
20050    cx.update_editor(|editor, window, cx| {
20051        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20052    });
20053
20054    cx.assert_state_with_diff(
20055        indoc! { "
20056            one
20057          - two
20058          + ˇTWO
20059            three
20060            four
20061            five
20062        "}
20063        .to_string(),
20064    );
20065    cx.update_editor(|editor, window, cx| {
20066        editor.move_up(&Default::default(), window, cx);
20067        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20068    });
20069    cx.assert_state_with_diff(
20070        indoc! { "
20071            one
20072            ˇTWO
20073            three
20074            four
20075            five
20076        "}
20077        .to_string(),
20078    );
20079}
20080
20081#[gpui::test]
20082async fn test_edits_around_expanded_deletion_hunks(
20083    executor: BackgroundExecutor,
20084    cx: &mut TestAppContext,
20085) {
20086    init_test(cx, |_| {});
20087
20088    let mut cx = EditorTestContext::new(cx).await;
20089
20090    let diff_base = r#"
20091        use some::mod1;
20092        use some::mod2;
20093
20094        const A: u32 = 42;
20095        const B: u32 = 42;
20096        const C: u32 = 42;
20097
20098
20099        fn main() {
20100            println!("hello");
20101
20102            println!("world");
20103        }
20104    "#
20105    .unindent();
20106    executor.run_until_parked();
20107    cx.set_state(
20108        &r#"
20109        use some::mod1;
20110        use some::mod2;
20111
20112        ˇconst B: u32 = 42;
20113        const C: u32 = 42;
20114
20115
20116        fn main() {
20117            println!("hello");
20118
20119            println!("world");
20120        }
20121        "#
20122        .unindent(),
20123    );
20124
20125    cx.set_head_text(&diff_base);
20126    executor.run_until_parked();
20127
20128    cx.update_editor(|editor, window, cx| {
20129        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20130    });
20131    executor.run_until_parked();
20132
20133    cx.assert_state_with_diff(
20134        r#"
20135        use some::mod1;
20136        use some::mod2;
20137
20138      - const A: u32 = 42;
20139        ˇconst B: u32 = 42;
20140        const C: u32 = 42;
20141
20142
20143        fn main() {
20144            println!("hello");
20145
20146            println!("world");
20147        }
20148      "#
20149        .unindent(),
20150    );
20151
20152    cx.update_editor(|editor, window, cx| {
20153        editor.delete_line(&DeleteLine, window, cx);
20154    });
20155    executor.run_until_parked();
20156    cx.assert_state_with_diff(
20157        r#"
20158        use some::mod1;
20159        use some::mod2;
20160
20161      - const A: u32 = 42;
20162      - const B: u32 = 42;
20163        ˇconst C: u32 = 42;
20164
20165
20166        fn main() {
20167            println!("hello");
20168
20169            println!("world");
20170        }
20171      "#
20172        .unindent(),
20173    );
20174
20175    cx.update_editor(|editor, window, cx| {
20176        editor.delete_line(&DeleteLine, window, cx);
20177    });
20178    executor.run_until_parked();
20179    cx.assert_state_with_diff(
20180        r#"
20181        use some::mod1;
20182        use some::mod2;
20183
20184      - const A: u32 = 42;
20185      - const B: u32 = 42;
20186      - const C: u32 = 42;
20187        ˇ
20188
20189        fn main() {
20190            println!("hello");
20191
20192            println!("world");
20193        }
20194      "#
20195        .unindent(),
20196    );
20197
20198    cx.update_editor(|editor, window, cx| {
20199        editor.handle_input("replacement", window, cx);
20200    });
20201    executor.run_until_parked();
20202    cx.assert_state_with_diff(
20203        r#"
20204        use some::mod1;
20205        use some::mod2;
20206
20207      - const A: u32 = 42;
20208      - const B: u32 = 42;
20209      - const C: u32 = 42;
20210      -
20211      + replacementˇ
20212
20213        fn main() {
20214            println!("hello");
20215
20216            println!("world");
20217        }
20218      "#
20219        .unindent(),
20220    );
20221}
20222
20223#[gpui::test]
20224async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
20225    init_test(cx, |_| {});
20226
20227    let mut cx = EditorTestContext::new(cx).await;
20228
20229    let base_text = r#"
20230        one
20231        two
20232        three
20233        four
20234        five
20235    "#
20236    .unindent();
20237    executor.run_until_parked();
20238    cx.set_state(
20239        &r#"
20240        one
20241        two
20242        fˇour
20243        five
20244        "#
20245        .unindent(),
20246    );
20247
20248    cx.set_head_text(&base_text);
20249    executor.run_until_parked();
20250
20251    cx.update_editor(|editor, window, cx| {
20252        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20253    });
20254    executor.run_until_parked();
20255
20256    cx.assert_state_with_diff(
20257        r#"
20258          one
20259          two
20260        - three
20261          fˇour
20262          five
20263        "#
20264        .unindent(),
20265    );
20266
20267    cx.update_editor(|editor, window, cx| {
20268        editor.backspace(&Backspace, window, cx);
20269        editor.backspace(&Backspace, window, cx);
20270    });
20271    executor.run_until_parked();
20272    cx.assert_state_with_diff(
20273        r#"
20274          one
20275          two
20276        - threeˇ
20277        - four
20278        + our
20279          five
20280        "#
20281        .unindent(),
20282    );
20283}
20284
20285#[gpui::test]
20286async fn test_edit_after_expanded_modification_hunk(
20287    executor: BackgroundExecutor,
20288    cx: &mut TestAppContext,
20289) {
20290    init_test(cx, |_| {});
20291
20292    let mut cx = EditorTestContext::new(cx).await;
20293
20294    let diff_base = r#"
20295        use some::mod1;
20296        use some::mod2;
20297
20298        const A: u32 = 42;
20299        const B: u32 = 42;
20300        const C: u32 = 42;
20301        const D: u32 = 42;
20302
20303
20304        fn main() {
20305            println!("hello");
20306
20307            println!("world");
20308        }"#
20309    .unindent();
20310
20311    cx.set_state(
20312        &r#"
20313        use some::mod1;
20314        use some::mod2;
20315
20316        const A: u32 = 42;
20317        const B: u32 = 42;
20318        const C: u32 = 43ˇ
20319        const D: u32 = 42;
20320
20321
20322        fn main() {
20323            println!("hello");
20324
20325            println!("world");
20326        }"#
20327        .unindent(),
20328    );
20329
20330    cx.set_head_text(&diff_base);
20331    executor.run_until_parked();
20332    cx.update_editor(|editor, window, cx| {
20333        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20334    });
20335    executor.run_until_parked();
20336
20337    cx.assert_state_with_diff(
20338        r#"
20339        use some::mod1;
20340        use some::mod2;
20341
20342        const A: u32 = 42;
20343        const B: u32 = 42;
20344      - const C: u32 = 42;
20345      + const C: u32 = 43ˇ
20346        const D: u32 = 42;
20347
20348
20349        fn main() {
20350            println!("hello");
20351
20352            println!("world");
20353        }"#
20354        .unindent(),
20355    );
20356
20357    cx.update_editor(|editor, window, cx| {
20358        editor.handle_input("\nnew_line\n", window, cx);
20359    });
20360    executor.run_until_parked();
20361
20362    cx.assert_state_with_diff(
20363        r#"
20364        use some::mod1;
20365        use some::mod2;
20366
20367        const A: u32 = 42;
20368        const B: u32 = 42;
20369      - const C: u32 = 42;
20370      + const C: u32 = 43
20371      + new_line
20372      + ˇ
20373        const D: u32 = 42;
20374
20375
20376        fn main() {
20377            println!("hello");
20378
20379            println!("world");
20380        }"#
20381        .unindent(),
20382    );
20383}
20384
20385#[gpui::test]
20386async fn test_stage_and_unstage_added_file_hunk(
20387    executor: BackgroundExecutor,
20388    cx: &mut TestAppContext,
20389) {
20390    init_test(cx, |_| {});
20391
20392    let mut cx = EditorTestContext::new(cx).await;
20393    cx.update_editor(|editor, _, cx| {
20394        editor.set_expand_all_diff_hunks(cx);
20395    });
20396
20397    let working_copy = r#"
20398            ˇfn main() {
20399                println!("hello, world!");
20400            }
20401        "#
20402    .unindent();
20403
20404    cx.set_state(&working_copy);
20405    executor.run_until_parked();
20406
20407    cx.assert_state_with_diff(
20408        r#"
20409            + ˇfn main() {
20410            +     println!("hello, world!");
20411            + }
20412        "#
20413        .unindent(),
20414    );
20415    cx.assert_index_text(None);
20416
20417    cx.update_editor(|editor, window, cx| {
20418        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20419    });
20420    executor.run_until_parked();
20421    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
20422    cx.assert_state_with_diff(
20423        r#"
20424            + ˇfn main() {
20425            +     println!("hello, world!");
20426            + }
20427        "#
20428        .unindent(),
20429    );
20430
20431    cx.update_editor(|editor, window, cx| {
20432        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20433    });
20434    executor.run_until_parked();
20435    cx.assert_index_text(None);
20436}
20437
20438async fn setup_indent_guides_editor(
20439    text: &str,
20440    cx: &mut TestAppContext,
20441) -> (BufferId, EditorTestContext) {
20442    init_test(cx, |_| {});
20443
20444    let mut cx = EditorTestContext::new(cx).await;
20445
20446    let buffer_id = cx.update_editor(|editor, window, cx| {
20447        editor.set_text(text, window, cx);
20448        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
20449
20450        buffer_ids[0]
20451    });
20452
20453    (buffer_id, cx)
20454}
20455
20456fn assert_indent_guides(
20457    range: Range<u32>,
20458    expected: Vec<IndentGuide>,
20459    active_indices: Option<Vec<usize>>,
20460    cx: &mut EditorTestContext,
20461) {
20462    let indent_guides = cx.update_editor(|editor, window, cx| {
20463        let snapshot = editor.snapshot(window, cx).display_snapshot;
20464        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
20465            editor,
20466            MultiBufferRow(range.start)..MultiBufferRow(range.end),
20467            true,
20468            &snapshot,
20469            cx,
20470        );
20471
20472        indent_guides.sort_by(|a, b| {
20473            a.depth.cmp(&b.depth).then(
20474                a.start_row
20475                    .cmp(&b.start_row)
20476                    .then(a.end_row.cmp(&b.end_row)),
20477            )
20478        });
20479        indent_guides
20480    });
20481
20482    if let Some(expected) = active_indices {
20483        let active_indices = cx.update_editor(|editor, window, cx| {
20484            let snapshot = editor.snapshot(window, cx).display_snapshot;
20485            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
20486        });
20487
20488        assert_eq!(
20489            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
20490            expected,
20491            "Active indent guide indices do not match"
20492        );
20493    }
20494
20495    assert_eq!(indent_guides, expected, "Indent guides do not match");
20496}
20497
20498fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
20499    IndentGuide {
20500        buffer_id,
20501        start_row: MultiBufferRow(start_row),
20502        end_row: MultiBufferRow(end_row),
20503        depth,
20504        tab_size: 4,
20505        settings: IndentGuideSettings {
20506            enabled: true,
20507            line_width: 1,
20508            active_line_width: 1,
20509            coloring: IndentGuideColoring::default(),
20510            background_coloring: IndentGuideBackgroundColoring::default(),
20511        },
20512    }
20513}
20514
20515#[gpui::test]
20516async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
20517    let (buffer_id, mut cx) = setup_indent_guides_editor(
20518        &"
20519        fn main() {
20520            let a = 1;
20521        }"
20522        .unindent(),
20523        cx,
20524    )
20525    .await;
20526
20527    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20528}
20529
20530#[gpui::test]
20531async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
20532    let (buffer_id, mut cx) = setup_indent_guides_editor(
20533        &"
20534        fn main() {
20535            let a = 1;
20536            let b = 2;
20537        }"
20538        .unindent(),
20539        cx,
20540    )
20541    .await;
20542
20543    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
20544}
20545
20546#[gpui::test]
20547async fn test_indent_guide_nested(cx: &mut TestAppContext) {
20548    let (buffer_id, mut cx) = setup_indent_guides_editor(
20549        &"
20550        fn main() {
20551            let a = 1;
20552            if a == 3 {
20553                let b = 2;
20554            } else {
20555                let c = 3;
20556            }
20557        }"
20558        .unindent(),
20559        cx,
20560    )
20561    .await;
20562
20563    assert_indent_guides(
20564        0..8,
20565        vec![
20566            indent_guide(buffer_id, 1, 6, 0),
20567            indent_guide(buffer_id, 3, 3, 1),
20568            indent_guide(buffer_id, 5, 5, 1),
20569        ],
20570        None,
20571        &mut cx,
20572    );
20573}
20574
20575#[gpui::test]
20576async fn test_indent_guide_tab(cx: &mut TestAppContext) {
20577    let (buffer_id, mut cx) = setup_indent_guides_editor(
20578        &"
20579        fn main() {
20580            let a = 1;
20581                let b = 2;
20582            let c = 3;
20583        }"
20584        .unindent(),
20585        cx,
20586    )
20587    .await;
20588
20589    assert_indent_guides(
20590        0..5,
20591        vec![
20592            indent_guide(buffer_id, 1, 3, 0),
20593            indent_guide(buffer_id, 2, 2, 1),
20594        ],
20595        None,
20596        &mut cx,
20597    );
20598}
20599
20600#[gpui::test]
20601async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
20602    let (buffer_id, mut cx) = setup_indent_guides_editor(
20603        &"
20604        fn main() {
20605            let a = 1;
20606
20607            let c = 3;
20608        }"
20609        .unindent(),
20610        cx,
20611    )
20612    .await;
20613
20614    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
20615}
20616
20617#[gpui::test]
20618async fn test_indent_guide_complex(cx: &mut TestAppContext) {
20619    let (buffer_id, mut cx) = setup_indent_guides_editor(
20620        &"
20621        fn main() {
20622            let a = 1;
20623
20624            let c = 3;
20625
20626            if a == 3 {
20627                let b = 2;
20628            } else {
20629                let c = 3;
20630            }
20631        }"
20632        .unindent(),
20633        cx,
20634    )
20635    .await;
20636
20637    assert_indent_guides(
20638        0..11,
20639        vec![
20640            indent_guide(buffer_id, 1, 9, 0),
20641            indent_guide(buffer_id, 6, 6, 1),
20642            indent_guide(buffer_id, 8, 8, 1),
20643        ],
20644        None,
20645        &mut cx,
20646    );
20647}
20648
20649#[gpui::test]
20650async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
20651    let (buffer_id, mut cx) = setup_indent_guides_editor(
20652        &"
20653        fn main() {
20654            let a = 1;
20655
20656            let c = 3;
20657
20658            if a == 3 {
20659                let b = 2;
20660            } else {
20661                let c = 3;
20662            }
20663        }"
20664        .unindent(),
20665        cx,
20666    )
20667    .await;
20668
20669    assert_indent_guides(
20670        1..11,
20671        vec![
20672            indent_guide(buffer_id, 1, 9, 0),
20673            indent_guide(buffer_id, 6, 6, 1),
20674            indent_guide(buffer_id, 8, 8, 1),
20675        ],
20676        None,
20677        &mut cx,
20678    );
20679}
20680
20681#[gpui::test]
20682async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
20683    let (buffer_id, mut cx) = setup_indent_guides_editor(
20684        &"
20685        fn main() {
20686            let a = 1;
20687
20688            let c = 3;
20689
20690            if a == 3 {
20691                let b = 2;
20692            } else {
20693                let c = 3;
20694            }
20695        }"
20696        .unindent(),
20697        cx,
20698    )
20699    .await;
20700
20701    assert_indent_guides(
20702        1..10,
20703        vec![
20704            indent_guide(buffer_id, 1, 9, 0),
20705            indent_guide(buffer_id, 6, 6, 1),
20706            indent_guide(buffer_id, 8, 8, 1),
20707        ],
20708        None,
20709        &mut cx,
20710    );
20711}
20712
20713#[gpui::test]
20714async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
20715    let (buffer_id, mut cx) = setup_indent_guides_editor(
20716        &"
20717        fn main() {
20718            if a {
20719                b(
20720                    c,
20721                    d,
20722                )
20723            } else {
20724                e(
20725                    f
20726                )
20727            }
20728        }"
20729        .unindent(),
20730        cx,
20731    )
20732    .await;
20733
20734    assert_indent_guides(
20735        0..11,
20736        vec![
20737            indent_guide(buffer_id, 1, 10, 0),
20738            indent_guide(buffer_id, 2, 5, 1),
20739            indent_guide(buffer_id, 7, 9, 1),
20740            indent_guide(buffer_id, 3, 4, 2),
20741            indent_guide(buffer_id, 8, 8, 2),
20742        ],
20743        None,
20744        &mut cx,
20745    );
20746
20747    cx.update_editor(|editor, window, cx| {
20748        editor.fold_at(MultiBufferRow(2), window, cx);
20749        assert_eq!(
20750            editor.display_text(cx),
20751            "
20752            fn main() {
20753                if a {
20754                    b(⋯
20755                    )
20756                } else {
20757                    e(
20758                        f
20759                    )
20760                }
20761            }"
20762            .unindent()
20763        );
20764    });
20765
20766    assert_indent_guides(
20767        0..11,
20768        vec![
20769            indent_guide(buffer_id, 1, 10, 0),
20770            indent_guide(buffer_id, 2, 5, 1),
20771            indent_guide(buffer_id, 7, 9, 1),
20772            indent_guide(buffer_id, 8, 8, 2),
20773        ],
20774        None,
20775        &mut cx,
20776    );
20777}
20778
20779#[gpui::test]
20780async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
20781    let (buffer_id, mut cx) = setup_indent_guides_editor(
20782        &"
20783        block1
20784            block2
20785                block3
20786                    block4
20787            block2
20788        block1
20789        block1"
20790            .unindent(),
20791        cx,
20792    )
20793    .await;
20794
20795    assert_indent_guides(
20796        1..10,
20797        vec![
20798            indent_guide(buffer_id, 1, 4, 0),
20799            indent_guide(buffer_id, 2, 3, 1),
20800            indent_guide(buffer_id, 3, 3, 2),
20801        ],
20802        None,
20803        &mut cx,
20804    );
20805}
20806
20807#[gpui::test]
20808async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
20809    let (buffer_id, mut cx) = setup_indent_guides_editor(
20810        &"
20811        block1
20812            block2
20813                block3
20814
20815        block1
20816        block1"
20817            .unindent(),
20818        cx,
20819    )
20820    .await;
20821
20822    assert_indent_guides(
20823        0..6,
20824        vec![
20825            indent_guide(buffer_id, 1, 2, 0),
20826            indent_guide(buffer_id, 2, 2, 1),
20827        ],
20828        None,
20829        &mut cx,
20830    );
20831}
20832
20833#[gpui::test]
20834async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
20835    let (buffer_id, mut cx) = setup_indent_guides_editor(
20836        &"
20837        function component() {
20838        \treturn (
20839        \t\t\t
20840        \t\t<div>
20841        \t\t\t<abc></abc>
20842        \t\t</div>
20843        \t)
20844        }"
20845        .unindent(),
20846        cx,
20847    )
20848    .await;
20849
20850    assert_indent_guides(
20851        0..8,
20852        vec![
20853            indent_guide(buffer_id, 1, 6, 0),
20854            indent_guide(buffer_id, 2, 5, 1),
20855            indent_guide(buffer_id, 4, 4, 2),
20856        ],
20857        None,
20858        &mut cx,
20859    );
20860}
20861
20862#[gpui::test]
20863async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
20864    let (buffer_id, mut cx) = setup_indent_guides_editor(
20865        &"
20866        function component() {
20867        \treturn (
20868        \t
20869        \t\t<div>
20870        \t\t\t<abc></abc>
20871        \t\t</div>
20872        \t)
20873        }"
20874        .unindent(),
20875        cx,
20876    )
20877    .await;
20878
20879    assert_indent_guides(
20880        0..8,
20881        vec![
20882            indent_guide(buffer_id, 1, 6, 0),
20883            indent_guide(buffer_id, 2, 5, 1),
20884            indent_guide(buffer_id, 4, 4, 2),
20885        ],
20886        None,
20887        &mut cx,
20888    );
20889}
20890
20891#[gpui::test]
20892async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
20893    let (buffer_id, mut cx) = setup_indent_guides_editor(
20894        &"
20895        block1
20896
20897
20898
20899            block2
20900        "
20901        .unindent(),
20902        cx,
20903    )
20904    .await;
20905
20906    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20907}
20908
20909#[gpui::test]
20910async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
20911    let (buffer_id, mut cx) = setup_indent_guides_editor(
20912        &"
20913        def a:
20914        \tb = 3
20915        \tif True:
20916        \t\tc = 4
20917        \t\td = 5
20918        \tprint(b)
20919        "
20920        .unindent(),
20921        cx,
20922    )
20923    .await;
20924
20925    assert_indent_guides(
20926        0..6,
20927        vec![
20928            indent_guide(buffer_id, 1, 5, 0),
20929            indent_guide(buffer_id, 3, 4, 1),
20930        ],
20931        None,
20932        &mut cx,
20933    );
20934}
20935
20936#[gpui::test]
20937async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
20938    let (buffer_id, mut cx) = setup_indent_guides_editor(
20939        &"
20940    fn main() {
20941        let a = 1;
20942    }"
20943        .unindent(),
20944        cx,
20945    )
20946    .await;
20947
20948    cx.update_editor(|editor, window, cx| {
20949        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20950            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20951        });
20952    });
20953
20954    assert_indent_guides(
20955        0..3,
20956        vec![indent_guide(buffer_id, 1, 1, 0)],
20957        Some(vec![0]),
20958        &mut cx,
20959    );
20960}
20961
20962#[gpui::test]
20963async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
20964    let (buffer_id, mut cx) = setup_indent_guides_editor(
20965        &"
20966    fn main() {
20967        if 1 == 2 {
20968            let a = 1;
20969        }
20970    }"
20971        .unindent(),
20972        cx,
20973    )
20974    .await;
20975
20976    cx.update_editor(|editor, window, cx| {
20977        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20978            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20979        });
20980    });
20981
20982    assert_indent_guides(
20983        0..4,
20984        vec![
20985            indent_guide(buffer_id, 1, 3, 0),
20986            indent_guide(buffer_id, 2, 2, 1),
20987        ],
20988        Some(vec![1]),
20989        &mut cx,
20990    );
20991
20992    cx.update_editor(|editor, window, cx| {
20993        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20994            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20995        });
20996    });
20997
20998    assert_indent_guides(
20999        0..4,
21000        vec![
21001            indent_guide(buffer_id, 1, 3, 0),
21002            indent_guide(buffer_id, 2, 2, 1),
21003        ],
21004        Some(vec![1]),
21005        &mut cx,
21006    );
21007
21008    cx.update_editor(|editor, window, cx| {
21009        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21010            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
21011        });
21012    });
21013
21014    assert_indent_guides(
21015        0..4,
21016        vec![
21017            indent_guide(buffer_id, 1, 3, 0),
21018            indent_guide(buffer_id, 2, 2, 1),
21019        ],
21020        Some(vec![0]),
21021        &mut cx,
21022    );
21023}
21024
21025#[gpui::test]
21026async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
21027    let (buffer_id, mut cx) = setup_indent_guides_editor(
21028        &"
21029    fn main() {
21030        let a = 1;
21031
21032        let b = 2;
21033    }"
21034        .unindent(),
21035        cx,
21036    )
21037    .await;
21038
21039    cx.update_editor(|editor, window, cx| {
21040        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21041            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
21042        });
21043    });
21044
21045    assert_indent_guides(
21046        0..5,
21047        vec![indent_guide(buffer_id, 1, 3, 0)],
21048        Some(vec![0]),
21049        &mut cx,
21050    );
21051}
21052
21053#[gpui::test]
21054async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
21055    let (buffer_id, mut cx) = setup_indent_guides_editor(
21056        &"
21057    def m:
21058        a = 1
21059        pass"
21060            .unindent(),
21061        cx,
21062    )
21063    .await;
21064
21065    cx.update_editor(|editor, window, cx| {
21066        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21067            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
21068        });
21069    });
21070
21071    assert_indent_guides(
21072        0..3,
21073        vec![indent_guide(buffer_id, 1, 2, 0)],
21074        Some(vec![0]),
21075        &mut cx,
21076    );
21077}
21078
21079#[gpui::test]
21080async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
21081    init_test(cx, |_| {});
21082    let mut cx = EditorTestContext::new(cx).await;
21083    let text = indoc! {
21084        "
21085        impl A {
21086            fn b() {
21087                0;
21088                3;
21089                5;
21090                6;
21091                7;
21092            }
21093        }
21094        "
21095    };
21096    let base_text = indoc! {
21097        "
21098        impl A {
21099            fn b() {
21100                0;
21101                1;
21102                2;
21103                3;
21104                4;
21105            }
21106            fn c() {
21107                5;
21108                6;
21109                7;
21110            }
21111        }
21112        "
21113    };
21114
21115    cx.update_editor(|editor, window, cx| {
21116        editor.set_text(text, window, cx);
21117
21118        editor.buffer().update(cx, |multibuffer, cx| {
21119            let buffer = multibuffer.as_singleton().unwrap();
21120            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
21121
21122            multibuffer.set_all_diff_hunks_expanded(cx);
21123            multibuffer.add_diff(diff, cx);
21124
21125            buffer.read(cx).remote_id()
21126        })
21127    });
21128    cx.run_until_parked();
21129
21130    cx.assert_state_with_diff(
21131        indoc! { "
21132          impl A {
21133              fn b() {
21134                  0;
21135        -         1;
21136        -         2;
21137                  3;
21138        -         4;
21139        -     }
21140        -     fn c() {
21141                  5;
21142                  6;
21143                  7;
21144              }
21145          }
21146          ˇ"
21147        }
21148        .to_string(),
21149    );
21150
21151    let mut actual_guides = cx.update_editor(|editor, window, cx| {
21152        editor
21153            .snapshot(window, cx)
21154            .buffer_snapshot()
21155            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
21156            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
21157            .collect::<Vec<_>>()
21158    });
21159    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
21160    assert_eq!(
21161        actual_guides,
21162        vec![
21163            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
21164            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
21165            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
21166        ]
21167    );
21168}
21169
21170#[gpui::test]
21171async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
21172    init_test(cx, |_| {});
21173    let mut cx = EditorTestContext::new(cx).await;
21174
21175    let diff_base = r#"
21176        a
21177        b
21178        c
21179        "#
21180    .unindent();
21181
21182    cx.set_state(
21183        &r#"
21184        ˇA
21185        b
21186        C
21187        "#
21188        .unindent(),
21189    );
21190    cx.set_head_text(&diff_base);
21191    cx.update_editor(|editor, window, cx| {
21192        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21193    });
21194    executor.run_until_parked();
21195
21196    let both_hunks_expanded = r#"
21197        - a
21198        + ˇA
21199          b
21200        - c
21201        + C
21202        "#
21203    .unindent();
21204
21205    cx.assert_state_with_diff(both_hunks_expanded.clone());
21206
21207    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21208        let snapshot = editor.snapshot(window, cx);
21209        let hunks = editor
21210            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21211            .collect::<Vec<_>>();
21212        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21213        let buffer_id = hunks[0].buffer_id;
21214        hunks
21215            .into_iter()
21216            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
21217            .collect::<Vec<_>>()
21218    });
21219    assert_eq!(hunk_ranges.len(), 2);
21220
21221    cx.update_editor(|editor, _, cx| {
21222        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21223    });
21224    executor.run_until_parked();
21225
21226    let second_hunk_expanded = r#"
21227          ˇA
21228          b
21229        - c
21230        + C
21231        "#
21232    .unindent();
21233
21234    cx.assert_state_with_diff(second_hunk_expanded);
21235
21236    cx.update_editor(|editor, _, cx| {
21237        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21238    });
21239    executor.run_until_parked();
21240
21241    cx.assert_state_with_diff(both_hunks_expanded.clone());
21242
21243    cx.update_editor(|editor, _, cx| {
21244        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21245    });
21246    executor.run_until_parked();
21247
21248    let first_hunk_expanded = r#"
21249        - a
21250        + ˇA
21251          b
21252          C
21253        "#
21254    .unindent();
21255
21256    cx.assert_state_with_diff(first_hunk_expanded);
21257
21258    cx.update_editor(|editor, _, cx| {
21259        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21260    });
21261    executor.run_until_parked();
21262
21263    cx.assert_state_with_diff(both_hunks_expanded);
21264
21265    cx.set_state(
21266        &r#"
21267        ˇA
21268        b
21269        "#
21270        .unindent(),
21271    );
21272    cx.run_until_parked();
21273
21274    // TODO this cursor position seems bad
21275    cx.assert_state_with_diff(
21276        r#"
21277        - ˇa
21278        + A
21279          b
21280        "#
21281        .unindent(),
21282    );
21283
21284    cx.update_editor(|editor, window, cx| {
21285        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21286    });
21287
21288    cx.assert_state_with_diff(
21289        r#"
21290            - ˇa
21291            + A
21292              b
21293            - c
21294            "#
21295        .unindent(),
21296    );
21297
21298    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21299        let snapshot = editor.snapshot(window, cx);
21300        let hunks = editor
21301            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21302            .collect::<Vec<_>>();
21303        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21304        let buffer_id = hunks[0].buffer_id;
21305        hunks
21306            .into_iter()
21307            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
21308            .collect::<Vec<_>>()
21309    });
21310    assert_eq!(hunk_ranges.len(), 2);
21311
21312    cx.update_editor(|editor, _, cx| {
21313        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21314    });
21315    executor.run_until_parked();
21316
21317    cx.assert_state_with_diff(
21318        r#"
21319        - ˇa
21320        + A
21321          b
21322        "#
21323        .unindent(),
21324    );
21325}
21326
21327#[gpui::test]
21328async fn test_toggle_deletion_hunk_at_start_of_file(
21329    executor: BackgroundExecutor,
21330    cx: &mut TestAppContext,
21331) {
21332    init_test(cx, |_| {});
21333    let mut cx = EditorTestContext::new(cx).await;
21334
21335    let diff_base = r#"
21336        a
21337        b
21338        c
21339        "#
21340    .unindent();
21341
21342    cx.set_state(
21343        &r#"
21344        ˇb
21345        c
21346        "#
21347        .unindent(),
21348    );
21349    cx.set_head_text(&diff_base);
21350    cx.update_editor(|editor, window, cx| {
21351        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21352    });
21353    executor.run_until_parked();
21354
21355    let hunk_expanded = r#"
21356        - a
21357          ˇb
21358          c
21359        "#
21360    .unindent();
21361
21362    cx.assert_state_with_diff(hunk_expanded.clone());
21363
21364    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21365        let snapshot = editor.snapshot(window, cx);
21366        let hunks = editor
21367            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21368            .collect::<Vec<_>>();
21369        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21370        let buffer_id = hunks[0].buffer_id;
21371        hunks
21372            .into_iter()
21373            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
21374            .collect::<Vec<_>>()
21375    });
21376    assert_eq!(hunk_ranges.len(), 1);
21377
21378    cx.update_editor(|editor, _, cx| {
21379        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21380    });
21381    executor.run_until_parked();
21382
21383    let hunk_collapsed = r#"
21384          ˇb
21385          c
21386        "#
21387    .unindent();
21388
21389    cx.assert_state_with_diff(hunk_collapsed);
21390
21391    cx.update_editor(|editor, _, cx| {
21392        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21393    });
21394    executor.run_until_parked();
21395
21396    cx.assert_state_with_diff(hunk_expanded);
21397}
21398
21399#[gpui::test]
21400async fn test_display_diff_hunks(cx: &mut TestAppContext) {
21401    init_test(cx, |_| {});
21402
21403    let fs = FakeFs::new(cx.executor());
21404    fs.insert_tree(
21405        path!("/test"),
21406        json!({
21407            ".git": {},
21408            "file-1": "ONE\n",
21409            "file-2": "TWO\n",
21410            "file-3": "THREE\n",
21411        }),
21412    )
21413    .await;
21414
21415    fs.set_head_for_repo(
21416        path!("/test/.git").as_ref(),
21417        &[
21418            ("file-1", "one\n".into()),
21419            ("file-2", "two\n".into()),
21420            ("file-3", "three\n".into()),
21421        ],
21422        "deadbeef",
21423    );
21424
21425    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
21426    let mut buffers = vec![];
21427    for i in 1..=3 {
21428        let buffer = project
21429            .update(cx, |project, cx| {
21430                let path = format!(path!("/test/file-{}"), i);
21431                project.open_local_buffer(path, cx)
21432            })
21433            .await
21434            .unwrap();
21435        buffers.push(buffer);
21436    }
21437
21438    let multibuffer = cx.new(|cx| {
21439        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
21440        multibuffer.set_all_diff_hunks_expanded(cx);
21441        for buffer in &buffers {
21442            let snapshot = buffer.read(cx).snapshot();
21443            multibuffer.set_excerpts_for_path(
21444                PathKey::with_sort_prefix(0, buffer.read(cx).file().unwrap().path().clone()),
21445                buffer.clone(),
21446                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
21447                2,
21448                cx,
21449            );
21450        }
21451        multibuffer
21452    });
21453
21454    let editor = cx.add_window(|window, cx| {
21455        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
21456    });
21457    cx.run_until_parked();
21458
21459    let snapshot = editor
21460        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21461        .unwrap();
21462    let hunks = snapshot
21463        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
21464        .map(|hunk| match hunk {
21465            DisplayDiffHunk::Unfolded {
21466                display_row_range, ..
21467            } => display_row_range,
21468            DisplayDiffHunk::Folded { .. } => unreachable!(),
21469        })
21470        .collect::<Vec<_>>();
21471    assert_eq!(
21472        hunks,
21473        [
21474            DisplayRow(2)..DisplayRow(4),
21475            DisplayRow(7)..DisplayRow(9),
21476            DisplayRow(12)..DisplayRow(14),
21477        ]
21478    );
21479}
21480
21481#[gpui::test]
21482async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
21483    init_test(cx, |_| {});
21484
21485    let mut cx = EditorTestContext::new(cx).await;
21486    cx.set_head_text(indoc! { "
21487        one
21488        two
21489        three
21490        four
21491        five
21492        "
21493    });
21494    cx.set_index_text(indoc! { "
21495        one
21496        two
21497        three
21498        four
21499        five
21500        "
21501    });
21502    cx.set_state(indoc! {"
21503        one
21504        TWO
21505        ˇTHREE
21506        FOUR
21507        five
21508    "});
21509    cx.run_until_parked();
21510    cx.update_editor(|editor, window, cx| {
21511        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21512    });
21513    cx.run_until_parked();
21514    cx.assert_index_text(Some(indoc! {"
21515        one
21516        TWO
21517        THREE
21518        FOUR
21519        five
21520    "}));
21521    cx.set_state(indoc! { "
21522        one
21523        TWO
21524        ˇTHREE-HUNDRED
21525        FOUR
21526        five
21527    "});
21528    cx.run_until_parked();
21529    cx.update_editor(|editor, window, cx| {
21530        let snapshot = editor.snapshot(window, cx);
21531        let hunks = editor
21532            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21533            .collect::<Vec<_>>();
21534        assert_eq!(hunks.len(), 1);
21535        assert_eq!(
21536            hunks[0].status(),
21537            DiffHunkStatus {
21538                kind: DiffHunkStatusKind::Modified,
21539                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
21540            }
21541        );
21542
21543        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21544    });
21545    cx.run_until_parked();
21546    cx.assert_index_text(Some(indoc! {"
21547        one
21548        TWO
21549        THREE-HUNDRED
21550        FOUR
21551        five
21552    "}));
21553}
21554
21555#[gpui::test]
21556fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
21557    init_test(cx, |_| {});
21558
21559    let editor = cx.add_window(|window, cx| {
21560        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
21561        build_editor(buffer, window, cx)
21562    });
21563
21564    let render_args = Arc::new(Mutex::new(None));
21565    let snapshot = editor
21566        .update(cx, |editor, window, cx| {
21567            let snapshot = editor.buffer().read(cx).snapshot(cx);
21568            let range =
21569                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
21570
21571            struct RenderArgs {
21572                row: MultiBufferRow,
21573                folded: bool,
21574                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
21575            }
21576
21577            let crease = Crease::inline(
21578                range,
21579                FoldPlaceholder::test(),
21580                {
21581                    let toggle_callback = render_args.clone();
21582                    move |row, folded, callback, _window, _cx| {
21583                        *toggle_callback.lock() = Some(RenderArgs {
21584                            row,
21585                            folded,
21586                            callback,
21587                        });
21588                        div()
21589                    }
21590                },
21591                |_row, _folded, _window, _cx| div(),
21592            );
21593
21594            editor.insert_creases(Some(crease), cx);
21595            let snapshot = editor.snapshot(window, cx);
21596            let _div =
21597                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
21598            snapshot
21599        })
21600        .unwrap();
21601
21602    let render_args = render_args.lock().take().unwrap();
21603    assert_eq!(render_args.row, MultiBufferRow(1));
21604    assert!(!render_args.folded);
21605    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21606
21607    cx.update_window(*editor, |_, window, cx| {
21608        (render_args.callback)(true, window, cx)
21609    })
21610    .unwrap();
21611    let snapshot = editor
21612        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21613        .unwrap();
21614    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
21615
21616    cx.update_window(*editor, |_, window, cx| {
21617        (render_args.callback)(false, window, cx)
21618    })
21619    .unwrap();
21620    let snapshot = editor
21621        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21622        .unwrap();
21623    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21624}
21625
21626#[gpui::test]
21627async fn test_input_text(cx: &mut TestAppContext) {
21628    init_test(cx, |_| {});
21629    let mut cx = EditorTestContext::new(cx).await;
21630
21631    cx.set_state(
21632        &r#"ˇone
21633        two
21634
21635        three
21636        fourˇ
21637        five
21638
21639        siˇx"#
21640            .unindent(),
21641    );
21642
21643    cx.dispatch_action(HandleInput(String::new()));
21644    cx.assert_editor_state(
21645        &r#"ˇone
21646        two
21647
21648        three
21649        fourˇ
21650        five
21651
21652        siˇx"#
21653            .unindent(),
21654    );
21655
21656    cx.dispatch_action(HandleInput("AAAA".to_string()));
21657    cx.assert_editor_state(
21658        &r#"AAAAˇone
21659        two
21660
21661        three
21662        fourAAAAˇ
21663        five
21664
21665        siAAAAˇx"#
21666            .unindent(),
21667    );
21668}
21669
21670#[gpui::test]
21671async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
21672    init_test(cx, |_| {});
21673
21674    let mut cx = EditorTestContext::new(cx).await;
21675    cx.set_state(
21676        r#"let foo = 1;
21677let foo = 2;
21678let foo = 3;
21679let fooˇ = 4;
21680let foo = 5;
21681let foo = 6;
21682let foo = 7;
21683let foo = 8;
21684let foo = 9;
21685let foo = 10;
21686let foo = 11;
21687let foo = 12;
21688let foo = 13;
21689let foo = 14;
21690let foo = 15;"#,
21691    );
21692
21693    cx.update_editor(|e, window, cx| {
21694        assert_eq!(
21695            e.next_scroll_position,
21696            NextScrollCursorCenterTopBottom::Center,
21697            "Default next scroll direction is center",
21698        );
21699
21700        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21701        assert_eq!(
21702            e.next_scroll_position,
21703            NextScrollCursorCenterTopBottom::Top,
21704            "After center, next scroll direction should be top",
21705        );
21706
21707        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21708        assert_eq!(
21709            e.next_scroll_position,
21710            NextScrollCursorCenterTopBottom::Bottom,
21711            "After top, next scroll direction should be bottom",
21712        );
21713
21714        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21715        assert_eq!(
21716            e.next_scroll_position,
21717            NextScrollCursorCenterTopBottom::Center,
21718            "After bottom, scrolling should start over",
21719        );
21720
21721        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21722        assert_eq!(
21723            e.next_scroll_position,
21724            NextScrollCursorCenterTopBottom::Top,
21725            "Scrolling continues if retriggered fast enough"
21726        );
21727    });
21728
21729    cx.executor()
21730        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
21731    cx.executor().run_until_parked();
21732    cx.update_editor(|e, _, _| {
21733        assert_eq!(
21734            e.next_scroll_position,
21735            NextScrollCursorCenterTopBottom::Center,
21736            "If scrolling is not triggered fast enough, it should reset"
21737        );
21738    });
21739}
21740
21741#[gpui::test]
21742async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
21743    init_test(cx, |_| {});
21744    let mut cx = EditorLspTestContext::new_rust(
21745        lsp::ServerCapabilities {
21746            definition_provider: Some(lsp::OneOf::Left(true)),
21747            references_provider: Some(lsp::OneOf::Left(true)),
21748            ..lsp::ServerCapabilities::default()
21749        },
21750        cx,
21751    )
21752    .await;
21753
21754    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
21755        let go_to_definition = cx
21756            .lsp
21757            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21758                move |params, _| async move {
21759                    if empty_go_to_definition {
21760                        Ok(None)
21761                    } else {
21762                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
21763                            uri: params.text_document_position_params.text_document.uri,
21764                            range: lsp::Range::new(
21765                                lsp::Position::new(4, 3),
21766                                lsp::Position::new(4, 6),
21767                            ),
21768                        })))
21769                    }
21770                },
21771            );
21772        let references = cx
21773            .lsp
21774            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21775                Ok(Some(vec![lsp::Location {
21776                    uri: params.text_document_position.text_document.uri,
21777                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
21778                }]))
21779            });
21780        (go_to_definition, references)
21781    };
21782
21783    cx.set_state(
21784        &r#"fn one() {
21785            let mut a = ˇtwo();
21786        }
21787
21788        fn two() {}"#
21789            .unindent(),
21790    );
21791    set_up_lsp_handlers(false, &mut cx);
21792    let navigated = cx
21793        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21794        .await
21795        .expect("Failed to navigate to definition");
21796    assert_eq!(
21797        navigated,
21798        Navigated::Yes,
21799        "Should have navigated to definition from the GetDefinition response"
21800    );
21801    cx.assert_editor_state(
21802        &r#"fn one() {
21803            let mut a = two();
21804        }
21805
21806        fn «twoˇ»() {}"#
21807            .unindent(),
21808    );
21809
21810    let editors = cx.update_workspace(|workspace, _, cx| {
21811        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21812    });
21813    cx.update_editor(|_, _, test_editor_cx| {
21814        assert_eq!(
21815            editors.len(),
21816            1,
21817            "Initially, only one, test, editor should be open in the workspace"
21818        );
21819        assert_eq!(
21820            test_editor_cx.entity(),
21821            editors.last().expect("Asserted len is 1").clone()
21822        );
21823    });
21824
21825    set_up_lsp_handlers(true, &mut cx);
21826    let navigated = cx
21827        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21828        .await
21829        .expect("Failed to navigate to lookup references");
21830    assert_eq!(
21831        navigated,
21832        Navigated::Yes,
21833        "Should have navigated to references as a fallback after empty GoToDefinition response"
21834    );
21835    // We should not change the selections in the existing file,
21836    // if opening another milti buffer with the references
21837    cx.assert_editor_state(
21838        &r#"fn one() {
21839            let mut a = two();
21840        }
21841
21842        fn «twoˇ»() {}"#
21843            .unindent(),
21844    );
21845    let editors = cx.update_workspace(|workspace, _, cx| {
21846        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21847    });
21848    cx.update_editor(|_, _, test_editor_cx| {
21849        assert_eq!(
21850            editors.len(),
21851            2,
21852            "After falling back to references search, we open a new editor with the results"
21853        );
21854        let references_fallback_text = editors
21855            .into_iter()
21856            .find(|new_editor| *new_editor != test_editor_cx.entity())
21857            .expect("Should have one non-test editor now")
21858            .read(test_editor_cx)
21859            .text(test_editor_cx);
21860        assert_eq!(
21861            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
21862            "Should use the range from the references response and not the GoToDefinition one"
21863        );
21864    });
21865}
21866
21867#[gpui::test]
21868async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
21869    init_test(cx, |_| {});
21870    cx.update(|cx| {
21871        let mut editor_settings = EditorSettings::get_global(cx).clone();
21872        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
21873        EditorSettings::override_global(editor_settings, cx);
21874    });
21875    let mut cx = EditorLspTestContext::new_rust(
21876        lsp::ServerCapabilities {
21877            definition_provider: Some(lsp::OneOf::Left(true)),
21878            references_provider: Some(lsp::OneOf::Left(true)),
21879            ..lsp::ServerCapabilities::default()
21880        },
21881        cx,
21882    )
21883    .await;
21884    let original_state = r#"fn one() {
21885        let mut a = ˇtwo();
21886    }
21887
21888    fn two() {}"#
21889        .unindent();
21890    cx.set_state(&original_state);
21891
21892    let mut go_to_definition = cx
21893        .lsp
21894        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21895            move |_, _| async move { Ok(None) },
21896        );
21897    let _references = cx
21898        .lsp
21899        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
21900            panic!("Should not call for references with no go to definition fallback")
21901        });
21902
21903    let navigated = cx
21904        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21905        .await
21906        .expect("Failed to navigate to lookup references");
21907    go_to_definition
21908        .next()
21909        .await
21910        .expect("Should have called the go_to_definition handler");
21911
21912    assert_eq!(
21913        navigated,
21914        Navigated::No,
21915        "Should have navigated to references as a fallback after empty GoToDefinition response"
21916    );
21917    cx.assert_editor_state(&original_state);
21918    let editors = cx.update_workspace(|workspace, _, cx| {
21919        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21920    });
21921    cx.update_editor(|_, _, _| {
21922        assert_eq!(
21923            editors.len(),
21924            1,
21925            "After unsuccessful fallback, no other editor should have been opened"
21926        );
21927    });
21928}
21929
21930#[gpui::test]
21931async fn test_find_all_references_editor_reuse(cx: &mut TestAppContext) {
21932    init_test(cx, |_| {});
21933    let mut cx = EditorLspTestContext::new_rust(
21934        lsp::ServerCapabilities {
21935            references_provider: Some(lsp::OneOf::Left(true)),
21936            ..lsp::ServerCapabilities::default()
21937        },
21938        cx,
21939    )
21940    .await;
21941
21942    cx.set_state(
21943        &r#"
21944        fn one() {
21945            let mut a = two();
21946        }
21947
21948        fn ˇtwo() {}"#
21949            .unindent(),
21950    );
21951    cx.lsp
21952        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21953            Ok(Some(vec![
21954                lsp::Location {
21955                    uri: params.text_document_position.text_document.uri.clone(),
21956                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
21957                },
21958                lsp::Location {
21959                    uri: params.text_document_position.text_document.uri,
21960                    range: lsp::Range::new(lsp::Position::new(4, 4), lsp::Position::new(4, 7)),
21961                },
21962            ]))
21963        });
21964    let navigated = cx
21965        .update_editor(|editor, window, cx| {
21966            editor.find_all_references(&FindAllReferences, window, cx)
21967        })
21968        .unwrap()
21969        .await
21970        .expect("Failed to navigate to references");
21971    assert_eq!(
21972        navigated,
21973        Navigated::Yes,
21974        "Should have navigated to references from the FindAllReferences response"
21975    );
21976    cx.assert_editor_state(
21977        &r#"fn one() {
21978            let mut a = two();
21979        }
21980
21981        fn ˇtwo() {}"#
21982            .unindent(),
21983    );
21984
21985    let editors = cx.update_workspace(|workspace, _, cx| {
21986        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21987    });
21988    cx.update_editor(|_, _, _| {
21989        assert_eq!(editors.len(), 2, "We should have opened a new multibuffer");
21990    });
21991
21992    cx.set_state(
21993        &r#"fn one() {
21994            let mut a = ˇtwo();
21995        }
21996
21997        fn two() {}"#
21998            .unindent(),
21999    );
22000    let navigated = cx
22001        .update_editor(|editor, window, cx| {
22002            editor.find_all_references(&FindAllReferences, window, cx)
22003        })
22004        .unwrap()
22005        .await
22006        .expect("Failed to navigate to references");
22007    assert_eq!(
22008        navigated,
22009        Navigated::Yes,
22010        "Should have navigated to references from the FindAllReferences response"
22011    );
22012    cx.assert_editor_state(
22013        &r#"fn one() {
22014            let mut a = ˇtwo();
22015        }
22016
22017        fn two() {}"#
22018            .unindent(),
22019    );
22020    let editors = cx.update_workspace(|workspace, _, cx| {
22021        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22022    });
22023    cx.update_editor(|_, _, _| {
22024        assert_eq!(
22025            editors.len(),
22026            2,
22027            "should have re-used the previous multibuffer"
22028        );
22029    });
22030
22031    cx.set_state(
22032        &r#"fn one() {
22033            let mut a = ˇtwo();
22034        }
22035        fn three() {}
22036        fn two() {}"#
22037            .unindent(),
22038    );
22039    cx.lsp
22040        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
22041            Ok(Some(vec![
22042                lsp::Location {
22043                    uri: params.text_document_position.text_document.uri.clone(),
22044                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
22045                },
22046                lsp::Location {
22047                    uri: params.text_document_position.text_document.uri,
22048                    range: lsp::Range::new(lsp::Position::new(5, 4), lsp::Position::new(5, 7)),
22049                },
22050            ]))
22051        });
22052    let navigated = cx
22053        .update_editor(|editor, window, cx| {
22054            editor.find_all_references(&FindAllReferences, window, cx)
22055        })
22056        .unwrap()
22057        .await
22058        .expect("Failed to navigate to references");
22059    assert_eq!(
22060        navigated,
22061        Navigated::Yes,
22062        "Should have navigated to references from the FindAllReferences response"
22063    );
22064    cx.assert_editor_state(
22065        &r#"fn one() {
22066                let mut a = ˇtwo();
22067            }
22068            fn three() {}
22069            fn two() {}"#
22070            .unindent(),
22071    );
22072    let editors = cx.update_workspace(|workspace, _, cx| {
22073        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22074    });
22075    cx.update_editor(|_, _, _| {
22076        assert_eq!(
22077            editors.len(),
22078            3,
22079            "should have used a new multibuffer as offsets changed"
22080        );
22081    });
22082}
22083#[gpui::test]
22084async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
22085    init_test(cx, |_| {});
22086
22087    let language = Arc::new(Language::new(
22088        LanguageConfig::default(),
22089        Some(tree_sitter_rust::LANGUAGE.into()),
22090    ));
22091
22092    let text = r#"
22093        #[cfg(test)]
22094        mod tests() {
22095            #[test]
22096            fn runnable_1() {
22097                let a = 1;
22098            }
22099
22100            #[test]
22101            fn runnable_2() {
22102                let a = 1;
22103                let b = 2;
22104            }
22105        }
22106    "#
22107    .unindent();
22108
22109    let fs = FakeFs::new(cx.executor());
22110    fs.insert_file("/file.rs", Default::default()).await;
22111
22112    let project = Project::test(fs, ["/a".as_ref()], cx).await;
22113    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22114    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22115    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
22116    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
22117
22118    let editor = cx.new_window_entity(|window, cx| {
22119        Editor::new(
22120            EditorMode::full(),
22121            multi_buffer,
22122            Some(project.clone()),
22123            window,
22124            cx,
22125        )
22126    });
22127
22128    editor.update_in(cx, |editor, window, cx| {
22129        let snapshot = editor.buffer().read(cx).snapshot(cx);
22130        editor.tasks.insert(
22131            (buffer.read(cx).remote_id(), 3),
22132            RunnableTasks {
22133                templates: vec![],
22134                offset: snapshot.anchor_before(43),
22135                column: 0,
22136                extra_variables: HashMap::default(),
22137                context_range: BufferOffset(43)..BufferOffset(85),
22138            },
22139        );
22140        editor.tasks.insert(
22141            (buffer.read(cx).remote_id(), 8),
22142            RunnableTasks {
22143                templates: vec![],
22144                offset: snapshot.anchor_before(86),
22145                column: 0,
22146                extra_variables: HashMap::default(),
22147                context_range: BufferOffset(86)..BufferOffset(191),
22148            },
22149        );
22150
22151        // Test finding task when cursor is inside function body
22152        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22153            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
22154        });
22155        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
22156        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
22157
22158        // Test finding task when cursor is on function name
22159        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22160            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
22161        });
22162        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
22163        assert_eq!(row, 8, "Should find task when cursor is on function name");
22164    });
22165}
22166
22167#[gpui::test]
22168async fn test_folding_buffers(cx: &mut TestAppContext) {
22169    init_test(cx, |_| {});
22170
22171    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
22172    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
22173    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
22174
22175    let fs = FakeFs::new(cx.executor());
22176    fs.insert_tree(
22177        path!("/a"),
22178        json!({
22179            "first.rs": sample_text_1,
22180            "second.rs": sample_text_2,
22181            "third.rs": sample_text_3,
22182        }),
22183    )
22184    .await;
22185    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22186    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22187    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22188    let worktree = project.update(cx, |project, cx| {
22189        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22190        assert_eq!(worktrees.len(), 1);
22191        worktrees.pop().unwrap()
22192    });
22193    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22194
22195    let buffer_1 = project
22196        .update(cx, |project, cx| {
22197            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
22198        })
22199        .await
22200        .unwrap();
22201    let buffer_2 = project
22202        .update(cx, |project, cx| {
22203            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
22204        })
22205        .await
22206        .unwrap();
22207    let buffer_3 = project
22208        .update(cx, |project, cx| {
22209            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
22210        })
22211        .await
22212        .unwrap();
22213
22214    let multi_buffer = cx.new(|cx| {
22215        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22216        multi_buffer.push_excerpts(
22217            buffer_1.clone(),
22218            [
22219                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
22220                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
22221                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
22222            ],
22223            cx,
22224        );
22225        multi_buffer.push_excerpts(
22226            buffer_2.clone(),
22227            [
22228                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
22229                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
22230                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
22231            ],
22232            cx,
22233        );
22234        multi_buffer.push_excerpts(
22235            buffer_3.clone(),
22236            [
22237                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
22238                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
22239                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
22240            ],
22241            cx,
22242        );
22243        multi_buffer
22244    });
22245    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22246        Editor::new(
22247            EditorMode::full(),
22248            multi_buffer.clone(),
22249            Some(project.clone()),
22250            window,
22251            cx,
22252        )
22253    });
22254
22255    assert_eq!(
22256        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22257        "\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",
22258    );
22259
22260    multi_buffer_editor.update(cx, |editor, cx| {
22261        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
22262    });
22263    assert_eq!(
22264        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22265        "\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",
22266        "After folding the first buffer, its text should not be displayed"
22267    );
22268
22269    multi_buffer_editor.update(cx, |editor, cx| {
22270        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
22271    });
22272    assert_eq!(
22273        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22274        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
22275        "After folding the second buffer, its text should not be displayed"
22276    );
22277
22278    multi_buffer_editor.update(cx, |editor, cx| {
22279        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
22280    });
22281    assert_eq!(
22282        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22283        "\n\n\n\n\n",
22284        "After folding the third buffer, its text should not be displayed"
22285    );
22286
22287    // Emulate selection inside the fold logic, that should work
22288    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22289        editor
22290            .snapshot(window, cx)
22291            .next_line_boundary(Point::new(0, 4));
22292    });
22293
22294    multi_buffer_editor.update(cx, |editor, cx| {
22295        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
22296    });
22297    assert_eq!(
22298        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22299        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
22300        "After unfolding the second buffer, its text should be displayed"
22301    );
22302
22303    // Typing inside of buffer 1 causes that buffer to be unfolded.
22304    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22305        assert_eq!(
22306            multi_buffer
22307                .read(cx)
22308                .snapshot(cx)
22309                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
22310                .collect::<String>(),
22311            "bbbb"
22312        );
22313        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22314            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
22315        });
22316        editor.handle_input("B", window, cx);
22317    });
22318
22319    assert_eq!(
22320        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22321        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
22322        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
22323    );
22324
22325    multi_buffer_editor.update(cx, |editor, cx| {
22326        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
22327    });
22328    assert_eq!(
22329        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22330        "\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",
22331        "After unfolding the all buffers, all original text should be displayed"
22332    );
22333}
22334
22335#[gpui::test]
22336async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
22337    init_test(cx, |_| {});
22338
22339    let sample_text_1 = "1111\n2222\n3333".to_string();
22340    let sample_text_2 = "4444\n5555\n6666".to_string();
22341    let sample_text_3 = "7777\n8888\n9999".to_string();
22342
22343    let fs = FakeFs::new(cx.executor());
22344    fs.insert_tree(
22345        path!("/a"),
22346        json!({
22347            "first.rs": sample_text_1,
22348            "second.rs": sample_text_2,
22349            "third.rs": sample_text_3,
22350        }),
22351    )
22352    .await;
22353    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22354    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22355    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22356    let worktree = project.update(cx, |project, cx| {
22357        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22358        assert_eq!(worktrees.len(), 1);
22359        worktrees.pop().unwrap()
22360    });
22361    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22362
22363    let buffer_1 = project
22364        .update(cx, |project, cx| {
22365            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
22366        })
22367        .await
22368        .unwrap();
22369    let buffer_2 = project
22370        .update(cx, |project, cx| {
22371            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
22372        })
22373        .await
22374        .unwrap();
22375    let buffer_3 = project
22376        .update(cx, |project, cx| {
22377            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
22378        })
22379        .await
22380        .unwrap();
22381
22382    let multi_buffer = cx.new(|cx| {
22383        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22384        multi_buffer.push_excerpts(
22385            buffer_1.clone(),
22386            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22387            cx,
22388        );
22389        multi_buffer.push_excerpts(
22390            buffer_2.clone(),
22391            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22392            cx,
22393        );
22394        multi_buffer.push_excerpts(
22395            buffer_3.clone(),
22396            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22397            cx,
22398        );
22399        multi_buffer
22400    });
22401
22402    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22403        Editor::new(
22404            EditorMode::full(),
22405            multi_buffer,
22406            Some(project.clone()),
22407            window,
22408            cx,
22409        )
22410    });
22411
22412    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
22413    assert_eq!(
22414        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22415        full_text,
22416    );
22417
22418    multi_buffer_editor.update(cx, |editor, cx| {
22419        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
22420    });
22421    assert_eq!(
22422        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22423        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
22424        "After folding the first buffer, its text should not be displayed"
22425    );
22426
22427    multi_buffer_editor.update(cx, |editor, cx| {
22428        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
22429    });
22430
22431    assert_eq!(
22432        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22433        "\n\n\n\n\n\n7777\n8888\n9999",
22434        "After folding the second buffer, its text should not be displayed"
22435    );
22436
22437    multi_buffer_editor.update(cx, |editor, cx| {
22438        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
22439    });
22440    assert_eq!(
22441        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22442        "\n\n\n\n\n",
22443        "After folding the third buffer, its text should not be displayed"
22444    );
22445
22446    multi_buffer_editor.update(cx, |editor, cx| {
22447        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
22448    });
22449    assert_eq!(
22450        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22451        "\n\n\n\n4444\n5555\n6666\n\n",
22452        "After unfolding the second buffer, its text should be displayed"
22453    );
22454
22455    multi_buffer_editor.update(cx, |editor, cx| {
22456        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
22457    });
22458    assert_eq!(
22459        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22460        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
22461        "After unfolding the first buffer, its text should be displayed"
22462    );
22463
22464    multi_buffer_editor.update(cx, |editor, cx| {
22465        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
22466    });
22467    assert_eq!(
22468        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22469        full_text,
22470        "After unfolding all buffers, all original text should be displayed"
22471    );
22472}
22473
22474#[gpui::test]
22475async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
22476    init_test(cx, |_| {});
22477
22478    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
22479
22480    let fs = FakeFs::new(cx.executor());
22481    fs.insert_tree(
22482        path!("/a"),
22483        json!({
22484            "main.rs": sample_text,
22485        }),
22486    )
22487    .await;
22488    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22489    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22490    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22491    let worktree = project.update(cx, |project, cx| {
22492        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22493        assert_eq!(worktrees.len(), 1);
22494        worktrees.pop().unwrap()
22495    });
22496    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22497
22498    let buffer_1 = project
22499        .update(cx, |project, cx| {
22500            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22501        })
22502        .await
22503        .unwrap();
22504
22505    let multi_buffer = cx.new(|cx| {
22506        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22507        multi_buffer.push_excerpts(
22508            buffer_1.clone(),
22509            [ExcerptRange::new(
22510                Point::new(0, 0)
22511                    ..Point::new(
22512                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
22513                        0,
22514                    ),
22515            )],
22516            cx,
22517        );
22518        multi_buffer
22519    });
22520    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22521        Editor::new(
22522            EditorMode::full(),
22523            multi_buffer,
22524            Some(project.clone()),
22525            window,
22526            cx,
22527        )
22528    });
22529
22530    let selection_range = Point::new(1, 0)..Point::new(2, 0);
22531    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22532        enum TestHighlight {}
22533        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
22534        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
22535        editor.highlight_text::<TestHighlight>(
22536            vec![highlight_range.clone()],
22537            HighlightStyle::color(Hsla::green()),
22538            cx,
22539        );
22540        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22541            s.select_ranges(Some(highlight_range))
22542        });
22543    });
22544
22545    let full_text = format!("\n\n{sample_text}");
22546    assert_eq!(
22547        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22548        full_text,
22549    );
22550}
22551
22552#[gpui::test]
22553async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
22554    init_test(cx, |_| {});
22555    cx.update(|cx| {
22556        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
22557            "keymaps/default-linux.json",
22558            cx,
22559        )
22560        .unwrap();
22561        cx.bind_keys(default_key_bindings);
22562    });
22563
22564    let (editor, cx) = cx.add_window_view(|window, cx| {
22565        let multi_buffer = MultiBuffer::build_multi(
22566            [
22567                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
22568                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
22569                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
22570                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
22571            ],
22572            cx,
22573        );
22574        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
22575
22576        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
22577        // fold all but the second buffer, so that we test navigating between two
22578        // adjacent folded buffers, as well as folded buffers at the start and
22579        // end the multibuffer
22580        editor.fold_buffer(buffer_ids[0], cx);
22581        editor.fold_buffer(buffer_ids[2], cx);
22582        editor.fold_buffer(buffer_ids[3], cx);
22583
22584        editor
22585    });
22586    cx.simulate_resize(size(px(1000.), px(1000.)));
22587
22588    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
22589    cx.assert_excerpts_with_selections(indoc! {"
22590        [EXCERPT]
22591        ˇ[FOLDED]
22592        [EXCERPT]
22593        a1
22594        b1
22595        [EXCERPT]
22596        [FOLDED]
22597        [EXCERPT]
22598        [FOLDED]
22599        "
22600    });
22601    cx.simulate_keystroke("down");
22602    cx.assert_excerpts_with_selections(indoc! {"
22603        [EXCERPT]
22604        [FOLDED]
22605        [EXCERPT]
22606        ˇa1
22607        b1
22608        [EXCERPT]
22609        [FOLDED]
22610        [EXCERPT]
22611        [FOLDED]
22612        "
22613    });
22614    cx.simulate_keystroke("down");
22615    cx.assert_excerpts_with_selections(indoc! {"
22616        [EXCERPT]
22617        [FOLDED]
22618        [EXCERPT]
22619        a1
22620        ˇb1
22621        [EXCERPT]
22622        [FOLDED]
22623        [EXCERPT]
22624        [FOLDED]
22625        "
22626    });
22627    cx.simulate_keystroke("down");
22628    cx.assert_excerpts_with_selections(indoc! {"
22629        [EXCERPT]
22630        [FOLDED]
22631        [EXCERPT]
22632        a1
22633        b1
22634        ˇ[EXCERPT]
22635        [FOLDED]
22636        [EXCERPT]
22637        [FOLDED]
22638        "
22639    });
22640    cx.simulate_keystroke("down");
22641    cx.assert_excerpts_with_selections(indoc! {"
22642        [EXCERPT]
22643        [FOLDED]
22644        [EXCERPT]
22645        a1
22646        b1
22647        [EXCERPT]
22648        ˇ[FOLDED]
22649        [EXCERPT]
22650        [FOLDED]
22651        "
22652    });
22653    for _ in 0..5 {
22654        cx.simulate_keystroke("down");
22655        cx.assert_excerpts_with_selections(indoc! {"
22656            [EXCERPT]
22657            [FOLDED]
22658            [EXCERPT]
22659            a1
22660            b1
22661            [EXCERPT]
22662            [FOLDED]
22663            [EXCERPT]
22664            ˇ[FOLDED]
22665            "
22666        });
22667    }
22668
22669    cx.simulate_keystroke("up");
22670    cx.assert_excerpts_with_selections(indoc! {"
22671        [EXCERPT]
22672        [FOLDED]
22673        [EXCERPT]
22674        a1
22675        b1
22676        [EXCERPT]
22677        ˇ[FOLDED]
22678        [EXCERPT]
22679        [FOLDED]
22680        "
22681    });
22682    cx.simulate_keystroke("up");
22683    cx.assert_excerpts_with_selections(indoc! {"
22684        [EXCERPT]
22685        [FOLDED]
22686        [EXCERPT]
22687        a1
22688        b1
22689        ˇ[EXCERPT]
22690        [FOLDED]
22691        [EXCERPT]
22692        [FOLDED]
22693        "
22694    });
22695    cx.simulate_keystroke("up");
22696    cx.assert_excerpts_with_selections(indoc! {"
22697        [EXCERPT]
22698        [FOLDED]
22699        [EXCERPT]
22700        a1
22701        ˇb1
22702        [EXCERPT]
22703        [FOLDED]
22704        [EXCERPT]
22705        [FOLDED]
22706        "
22707    });
22708    cx.simulate_keystroke("up");
22709    cx.assert_excerpts_with_selections(indoc! {"
22710        [EXCERPT]
22711        [FOLDED]
22712        [EXCERPT]
22713        ˇa1
22714        b1
22715        [EXCERPT]
22716        [FOLDED]
22717        [EXCERPT]
22718        [FOLDED]
22719        "
22720    });
22721    for _ in 0..5 {
22722        cx.simulate_keystroke("up");
22723        cx.assert_excerpts_with_selections(indoc! {"
22724            [EXCERPT]
22725            ˇ[FOLDED]
22726            [EXCERPT]
22727            a1
22728            b1
22729            [EXCERPT]
22730            [FOLDED]
22731            [EXCERPT]
22732            [FOLDED]
22733            "
22734        });
22735    }
22736}
22737
22738#[gpui::test]
22739async fn test_edit_prediction_text(cx: &mut TestAppContext) {
22740    init_test(cx, |_| {});
22741
22742    // Simple insertion
22743    assert_highlighted_edits(
22744        "Hello, world!",
22745        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
22746        true,
22747        cx,
22748        |highlighted_edits, cx| {
22749            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
22750            assert_eq!(highlighted_edits.highlights.len(), 1);
22751            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
22752            assert_eq!(
22753                highlighted_edits.highlights[0].1.background_color,
22754                Some(cx.theme().status().created_background)
22755            );
22756        },
22757    )
22758    .await;
22759
22760    // Replacement
22761    assert_highlighted_edits(
22762        "This is a test.",
22763        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
22764        false,
22765        cx,
22766        |highlighted_edits, cx| {
22767            assert_eq!(highlighted_edits.text, "That is a test.");
22768            assert_eq!(highlighted_edits.highlights.len(), 1);
22769            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
22770            assert_eq!(
22771                highlighted_edits.highlights[0].1.background_color,
22772                Some(cx.theme().status().created_background)
22773            );
22774        },
22775    )
22776    .await;
22777
22778    // Multiple edits
22779    assert_highlighted_edits(
22780        "Hello, world!",
22781        vec![
22782            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
22783            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
22784        ],
22785        false,
22786        cx,
22787        |highlighted_edits, cx| {
22788            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
22789            assert_eq!(highlighted_edits.highlights.len(), 2);
22790            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
22791            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
22792            assert_eq!(
22793                highlighted_edits.highlights[0].1.background_color,
22794                Some(cx.theme().status().created_background)
22795            );
22796            assert_eq!(
22797                highlighted_edits.highlights[1].1.background_color,
22798                Some(cx.theme().status().created_background)
22799            );
22800        },
22801    )
22802    .await;
22803
22804    // Multiple lines with edits
22805    assert_highlighted_edits(
22806        "First line\nSecond line\nThird line\nFourth line",
22807        vec![
22808            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
22809            (
22810                Point::new(2, 0)..Point::new(2, 10),
22811                "New third line".to_string(),
22812            ),
22813            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
22814        ],
22815        false,
22816        cx,
22817        |highlighted_edits, cx| {
22818            assert_eq!(
22819                highlighted_edits.text,
22820                "Second modified\nNew third line\nFourth updated line"
22821            );
22822            assert_eq!(highlighted_edits.highlights.len(), 3);
22823            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
22824            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
22825            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
22826            for highlight in &highlighted_edits.highlights {
22827                assert_eq!(
22828                    highlight.1.background_color,
22829                    Some(cx.theme().status().created_background)
22830                );
22831            }
22832        },
22833    )
22834    .await;
22835}
22836
22837#[gpui::test]
22838async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
22839    init_test(cx, |_| {});
22840
22841    // Deletion
22842    assert_highlighted_edits(
22843        "Hello, world!",
22844        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
22845        true,
22846        cx,
22847        |highlighted_edits, cx| {
22848            assert_eq!(highlighted_edits.text, "Hello, world!");
22849            assert_eq!(highlighted_edits.highlights.len(), 1);
22850            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
22851            assert_eq!(
22852                highlighted_edits.highlights[0].1.background_color,
22853                Some(cx.theme().status().deleted_background)
22854            );
22855        },
22856    )
22857    .await;
22858
22859    // Insertion
22860    assert_highlighted_edits(
22861        "Hello, world!",
22862        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
22863        true,
22864        cx,
22865        |highlighted_edits, cx| {
22866            assert_eq!(highlighted_edits.highlights.len(), 1);
22867            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
22868            assert_eq!(
22869                highlighted_edits.highlights[0].1.background_color,
22870                Some(cx.theme().status().created_background)
22871            );
22872        },
22873    )
22874    .await;
22875}
22876
22877async fn assert_highlighted_edits(
22878    text: &str,
22879    edits: Vec<(Range<Point>, String)>,
22880    include_deletions: bool,
22881    cx: &mut TestAppContext,
22882    assertion_fn: impl Fn(HighlightedText, &App),
22883) {
22884    let window = cx.add_window(|window, cx| {
22885        let buffer = MultiBuffer::build_simple(text, cx);
22886        Editor::new(EditorMode::full(), buffer, None, window, cx)
22887    });
22888    let cx = &mut VisualTestContext::from_window(*window, cx);
22889
22890    let (buffer, snapshot) = window
22891        .update(cx, |editor, _window, cx| {
22892            (
22893                editor.buffer().clone(),
22894                editor.buffer().read(cx).snapshot(cx),
22895            )
22896        })
22897        .unwrap();
22898
22899    let edits = edits
22900        .into_iter()
22901        .map(|(range, edit)| {
22902            (
22903                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
22904                edit,
22905            )
22906        })
22907        .collect::<Vec<_>>();
22908
22909    let text_anchor_edits = edits
22910        .clone()
22911        .into_iter()
22912        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
22913        .collect::<Vec<_>>();
22914
22915    let edit_preview = window
22916        .update(cx, |_, _window, cx| {
22917            buffer
22918                .read(cx)
22919                .as_singleton()
22920                .unwrap()
22921                .read(cx)
22922                .preview_edits(text_anchor_edits.into(), cx)
22923        })
22924        .unwrap()
22925        .await;
22926
22927    cx.update(|_window, cx| {
22928        let highlighted_edits = edit_prediction_edit_text(
22929            snapshot.as_singleton().unwrap().2,
22930            &edits,
22931            &edit_preview,
22932            include_deletions,
22933            cx,
22934        );
22935        assertion_fn(highlighted_edits, cx)
22936    });
22937}
22938
22939#[track_caller]
22940fn assert_breakpoint(
22941    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
22942    path: &Arc<Path>,
22943    expected: Vec<(u32, Breakpoint)>,
22944) {
22945    if expected.is_empty() {
22946        assert!(!breakpoints.contains_key(path), "{}", path.display());
22947    } else {
22948        let mut breakpoint = breakpoints
22949            .get(path)
22950            .unwrap()
22951            .iter()
22952            .map(|breakpoint| {
22953                (
22954                    breakpoint.row,
22955                    Breakpoint {
22956                        message: breakpoint.message.clone(),
22957                        state: breakpoint.state,
22958                        condition: breakpoint.condition.clone(),
22959                        hit_condition: breakpoint.hit_condition.clone(),
22960                    },
22961                )
22962            })
22963            .collect::<Vec<_>>();
22964
22965        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
22966
22967        assert_eq!(expected, breakpoint);
22968    }
22969}
22970
22971fn add_log_breakpoint_at_cursor(
22972    editor: &mut Editor,
22973    log_message: &str,
22974    window: &mut Window,
22975    cx: &mut Context<Editor>,
22976) {
22977    let (anchor, bp) = editor
22978        .breakpoints_at_cursors(window, cx)
22979        .first()
22980        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
22981        .unwrap_or_else(|| {
22982            let snapshot = editor.snapshot(window, cx);
22983            let cursor_position: Point =
22984                editor.selections.newest(&snapshot.display_snapshot).head();
22985
22986            let breakpoint_position = snapshot
22987                .buffer_snapshot()
22988                .anchor_before(Point::new(cursor_position.row, 0));
22989
22990            (breakpoint_position, Breakpoint::new_log(log_message))
22991        });
22992
22993    editor.edit_breakpoint_at_anchor(
22994        anchor,
22995        bp,
22996        BreakpointEditAction::EditLogMessage(log_message.into()),
22997        cx,
22998    );
22999}
23000
23001#[gpui::test]
23002async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
23003    init_test(cx, |_| {});
23004
23005    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
23006    let fs = FakeFs::new(cx.executor());
23007    fs.insert_tree(
23008        path!("/a"),
23009        json!({
23010            "main.rs": sample_text,
23011        }),
23012    )
23013    .await;
23014    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23015    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23016    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23017
23018    let fs = FakeFs::new(cx.executor());
23019    fs.insert_tree(
23020        path!("/a"),
23021        json!({
23022            "main.rs": sample_text,
23023        }),
23024    )
23025    .await;
23026    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23027    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23028    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23029    let worktree_id = workspace
23030        .update(cx, |workspace, _window, cx| {
23031            workspace.project().update(cx, |project, cx| {
23032                project.worktrees(cx).next().unwrap().read(cx).id()
23033            })
23034        })
23035        .unwrap();
23036
23037    let buffer = project
23038        .update(cx, |project, cx| {
23039            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23040        })
23041        .await
23042        .unwrap();
23043
23044    let (editor, cx) = cx.add_window_view(|window, cx| {
23045        Editor::new(
23046            EditorMode::full(),
23047            MultiBuffer::build_from_buffer(buffer, cx),
23048            Some(project.clone()),
23049            window,
23050            cx,
23051        )
23052    });
23053
23054    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
23055    let abs_path = project.read_with(cx, |project, cx| {
23056        project
23057            .absolute_path(&project_path, cx)
23058            .map(Arc::from)
23059            .unwrap()
23060    });
23061
23062    // assert we can add breakpoint on the first line
23063    editor.update_in(cx, |editor, window, cx| {
23064        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23065        editor.move_to_end(&MoveToEnd, window, cx);
23066        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23067    });
23068
23069    let breakpoints = editor.update(cx, |editor, cx| {
23070        editor
23071            .breakpoint_store()
23072            .as_ref()
23073            .unwrap()
23074            .read(cx)
23075            .all_source_breakpoints(cx)
23076    });
23077
23078    assert_eq!(1, breakpoints.len());
23079    assert_breakpoint(
23080        &breakpoints,
23081        &abs_path,
23082        vec![
23083            (0, Breakpoint::new_standard()),
23084            (3, Breakpoint::new_standard()),
23085        ],
23086    );
23087
23088    editor.update_in(cx, |editor, window, cx| {
23089        editor.move_to_beginning(&MoveToBeginning, window, cx);
23090        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23091    });
23092
23093    let breakpoints = editor.update(cx, |editor, cx| {
23094        editor
23095            .breakpoint_store()
23096            .as_ref()
23097            .unwrap()
23098            .read(cx)
23099            .all_source_breakpoints(cx)
23100    });
23101
23102    assert_eq!(1, breakpoints.len());
23103    assert_breakpoint(
23104        &breakpoints,
23105        &abs_path,
23106        vec![(3, Breakpoint::new_standard())],
23107    );
23108
23109    editor.update_in(cx, |editor, window, cx| {
23110        editor.move_to_end(&MoveToEnd, window, cx);
23111        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23112    });
23113
23114    let breakpoints = editor.update(cx, |editor, cx| {
23115        editor
23116            .breakpoint_store()
23117            .as_ref()
23118            .unwrap()
23119            .read(cx)
23120            .all_source_breakpoints(cx)
23121    });
23122
23123    assert_eq!(0, breakpoints.len());
23124    assert_breakpoint(&breakpoints, &abs_path, vec![]);
23125}
23126
23127#[gpui::test]
23128async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
23129    init_test(cx, |_| {});
23130
23131    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
23132
23133    let fs = FakeFs::new(cx.executor());
23134    fs.insert_tree(
23135        path!("/a"),
23136        json!({
23137            "main.rs": sample_text,
23138        }),
23139    )
23140    .await;
23141    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23142    let (workspace, cx) =
23143        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23144
23145    let worktree_id = workspace.update(cx, |workspace, cx| {
23146        workspace.project().update(cx, |project, cx| {
23147            project.worktrees(cx).next().unwrap().read(cx).id()
23148        })
23149    });
23150
23151    let buffer = project
23152        .update(cx, |project, cx| {
23153            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23154        })
23155        .await
23156        .unwrap();
23157
23158    let (editor, cx) = cx.add_window_view(|window, cx| {
23159        Editor::new(
23160            EditorMode::full(),
23161            MultiBuffer::build_from_buffer(buffer, cx),
23162            Some(project.clone()),
23163            window,
23164            cx,
23165        )
23166    });
23167
23168    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
23169    let abs_path = project.read_with(cx, |project, cx| {
23170        project
23171            .absolute_path(&project_path, cx)
23172            .map(Arc::from)
23173            .unwrap()
23174    });
23175
23176    editor.update_in(cx, |editor, window, cx| {
23177        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
23178    });
23179
23180    let breakpoints = editor.update(cx, |editor, cx| {
23181        editor
23182            .breakpoint_store()
23183            .as_ref()
23184            .unwrap()
23185            .read(cx)
23186            .all_source_breakpoints(cx)
23187    });
23188
23189    assert_breakpoint(
23190        &breakpoints,
23191        &abs_path,
23192        vec![(0, Breakpoint::new_log("hello world"))],
23193    );
23194
23195    // Removing a log message from a log breakpoint should remove it
23196    editor.update_in(cx, |editor, window, cx| {
23197        add_log_breakpoint_at_cursor(editor, "", window, cx);
23198    });
23199
23200    let breakpoints = editor.update(cx, |editor, cx| {
23201        editor
23202            .breakpoint_store()
23203            .as_ref()
23204            .unwrap()
23205            .read(cx)
23206            .all_source_breakpoints(cx)
23207    });
23208
23209    assert_breakpoint(&breakpoints, &abs_path, vec![]);
23210
23211    editor.update_in(cx, |editor, window, cx| {
23212        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23213        editor.move_to_end(&MoveToEnd, window, cx);
23214        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23215        // Not adding a log message to a standard breakpoint shouldn't remove it
23216        add_log_breakpoint_at_cursor(editor, "", window, cx);
23217    });
23218
23219    let breakpoints = editor.update(cx, |editor, cx| {
23220        editor
23221            .breakpoint_store()
23222            .as_ref()
23223            .unwrap()
23224            .read(cx)
23225            .all_source_breakpoints(cx)
23226    });
23227
23228    assert_breakpoint(
23229        &breakpoints,
23230        &abs_path,
23231        vec![
23232            (0, Breakpoint::new_standard()),
23233            (3, Breakpoint::new_standard()),
23234        ],
23235    );
23236
23237    editor.update_in(cx, |editor, window, cx| {
23238        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
23239    });
23240
23241    let breakpoints = editor.update(cx, |editor, cx| {
23242        editor
23243            .breakpoint_store()
23244            .as_ref()
23245            .unwrap()
23246            .read(cx)
23247            .all_source_breakpoints(cx)
23248    });
23249
23250    assert_breakpoint(
23251        &breakpoints,
23252        &abs_path,
23253        vec![
23254            (0, Breakpoint::new_standard()),
23255            (3, Breakpoint::new_log("hello world")),
23256        ],
23257    );
23258
23259    editor.update_in(cx, |editor, window, cx| {
23260        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
23261    });
23262
23263    let breakpoints = editor.update(cx, |editor, cx| {
23264        editor
23265            .breakpoint_store()
23266            .as_ref()
23267            .unwrap()
23268            .read(cx)
23269            .all_source_breakpoints(cx)
23270    });
23271
23272    assert_breakpoint(
23273        &breakpoints,
23274        &abs_path,
23275        vec![
23276            (0, Breakpoint::new_standard()),
23277            (3, Breakpoint::new_log("hello Earth!!")),
23278        ],
23279    );
23280}
23281
23282/// This also tests that Editor::breakpoint_at_cursor_head is working properly
23283/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
23284/// or when breakpoints were placed out of order. This tests for a regression too
23285#[gpui::test]
23286async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
23287    init_test(cx, |_| {});
23288
23289    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
23290    let fs = FakeFs::new(cx.executor());
23291    fs.insert_tree(
23292        path!("/a"),
23293        json!({
23294            "main.rs": sample_text,
23295        }),
23296    )
23297    .await;
23298    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23299    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23300    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23301
23302    let fs = FakeFs::new(cx.executor());
23303    fs.insert_tree(
23304        path!("/a"),
23305        json!({
23306            "main.rs": sample_text,
23307        }),
23308    )
23309    .await;
23310    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23311    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23312    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23313    let worktree_id = workspace
23314        .update(cx, |workspace, _window, cx| {
23315            workspace.project().update(cx, |project, cx| {
23316                project.worktrees(cx).next().unwrap().read(cx).id()
23317            })
23318        })
23319        .unwrap();
23320
23321    let buffer = project
23322        .update(cx, |project, cx| {
23323            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23324        })
23325        .await
23326        .unwrap();
23327
23328    let (editor, cx) = cx.add_window_view(|window, cx| {
23329        Editor::new(
23330            EditorMode::full(),
23331            MultiBuffer::build_from_buffer(buffer, cx),
23332            Some(project.clone()),
23333            window,
23334            cx,
23335        )
23336    });
23337
23338    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
23339    let abs_path = project.read_with(cx, |project, cx| {
23340        project
23341            .absolute_path(&project_path, cx)
23342            .map(Arc::from)
23343            .unwrap()
23344    });
23345
23346    // assert we can add breakpoint on the first line
23347    editor.update_in(cx, |editor, window, cx| {
23348        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23349        editor.move_to_end(&MoveToEnd, window, cx);
23350        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23351        editor.move_up(&MoveUp, window, cx);
23352        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23353    });
23354
23355    let breakpoints = editor.update(cx, |editor, cx| {
23356        editor
23357            .breakpoint_store()
23358            .as_ref()
23359            .unwrap()
23360            .read(cx)
23361            .all_source_breakpoints(cx)
23362    });
23363
23364    assert_eq!(1, breakpoints.len());
23365    assert_breakpoint(
23366        &breakpoints,
23367        &abs_path,
23368        vec![
23369            (0, Breakpoint::new_standard()),
23370            (2, Breakpoint::new_standard()),
23371            (3, Breakpoint::new_standard()),
23372        ],
23373    );
23374
23375    editor.update_in(cx, |editor, window, cx| {
23376        editor.move_to_beginning(&MoveToBeginning, window, cx);
23377        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23378        editor.move_to_end(&MoveToEnd, window, cx);
23379        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23380        // Disabling a breakpoint that doesn't exist should do nothing
23381        editor.move_up(&MoveUp, window, cx);
23382        editor.move_up(&MoveUp, window, cx);
23383        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23384    });
23385
23386    let breakpoints = editor.update(cx, |editor, cx| {
23387        editor
23388            .breakpoint_store()
23389            .as_ref()
23390            .unwrap()
23391            .read(cx)
23392            .all_source_breakpoints(cx)
23393    });
23394
23395    let disable_breakpoint = {
23396        let mut bp = Breakpoint::new_standard();
23397        bp.state = BreakpointState::Disabled;
23398        bp
23399    };
23400
23401    assert_eq!(1, breakpoints.len());
23402    assert_breakpoint(
23403        &breakpoints,
23404        &abs_path,
23405        vec![
23406            (0, disable_breakpoint.clone()),
23407            (2, Breakpoint::new_standard()),
23408            (3, disable_breakpoint.clone()),
23409        ],
23410    );
23411
23412    editor.update_in(cx, |editor, window, cx| {
23413        editor.move_to_beginning(&MoveToBeginning, window, cx);
23414        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
23415        editor.move_to_end(&MoveToEnd, window, cx);
23416        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
23417        editor.move_up(&MoveUp, window, cx);
23418        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23419    });
23420
23421    let breakpoints = editor.update(cx, |editor, cx| {
23422        editor
23423            .breakpoint_store()
23424            .as_ref()
23425            .unwrap()
23426            .read(cx)
23427            .all_source_breakpoints(cx)
23428    });
23429
23430    assert_eq!(1, breakpoints.len());
23431    assert_breakpoint(
23432        &breakpoints,
23433        &abs_path,
23434        vec![
23435            (0, Breakpoint::new_standard()),
23436            (2, disable_breakpoint),
23437            (3, Breakpoint::new_standard()),
23438        ],
23439    );
23440}
23441
23442#[gpui::test]
23443async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
23444    init_test(cx, |_| {});
23445    let capabilities = lsp::ServerCapabilities {
23446        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
23447            prepare_provider: Some(true),
23448            work_done_progress_options: Default::default(),
23449        })),
23450        ..Default::default()
23451    };
23452    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
23453
23454    cx.set_state(indoc! {"
23455        struct Fˇoo {}
23456    "});
23457
23458    cx.update_editor(|editor, _, cx| {
23459        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
23460        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
23461        editor.highlight_background::<DocumentHighlightRead>(
23462            &[highlight_range],
23463            |theme| theme.colors().editor_document_highlight_read_background,
23464            cx,
23465        );
23466    });
23467
23468    let mut prepare_rename_handler = cx
23469        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
23470            move |_, _, _| async move {
23471                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
23472                    start: lsp::Position {
23473                        line: 0,
23474                        character: 7,
23475                    },
23476                    end: lsp::Position {
23477                        line: 0,
23478                        character: 10,
23479                    },
23480                })))
23481            },
23482        );
23483    let prepare_rename_task = cx
23484        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23485        .expect("Prepare rename was not started");
23486    prepare_rename_handler.next().await.unwrap();
23487    prepare_rename_task.await.expect("Prepare rename failed");
23488
23489    let mut rename_handler =
23490        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23491            let edit = lsp::TextEdit {
23492                range: lsp::Range {
23493                    start: lsp::Position {
23494                        line: 0,
23495                        character: 7,
23496                    },
23497                    end: lsp::Position {
23498                        line: 0,
23499                        character: 10,
23500                    },
23501                },
23502                new_text: "FooRenamed".to_string(),
23503            };
23504            Ok(Some(lsp::WorkspaceEdit::new(
23505                // Specify the same edit twice
23506                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
23507            )))
23508        });
23509    let rename_task = cx
23510        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23511        .expect("Confirm rename was not started");
23512    rename_handler.next().await.unwrap();
23513    rename_task.await.expect("Confirm rename failed");
23514    cx.run_until_parked();
23515
23516    // Despite two edits, only one is actually applied as those are identical
23517    cx.assert_editor_state(indoc! {"
23518        struct FooRenamedˇ {}
23519    "});
23520}
23521
23522#[gpui::test]
23523async fn test_rename_without_prepare(cx: &mut TestAppContext) {
23524    init_test(cx, |_| {});
23525    // These capabilities indicate that the server does not support prepare rename.
23526    let capabilities = lsp::ServerCapabilities {
23527        rename_provider: Some(lsp::OneOf::Left(true)),
23528        ..Default::default()
23529    };
23530    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
23531
23532    cx.set_state(indoc! {"
23533        struct Fˇoo {}
23534    "});
23535
23536    cx.update_editor(|editor, _window, cx| {
23537        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
23538        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
23539        editor.highlight_background::<DocumentHighlightRead>(
23540            &[highlight_range],
23541            |theme| theme.colors().editor_document_highlight_read_background,
23542            cx,
23543        );
23544    });
23545
23546    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23547        .expect("Prepare rename was not started")
23548        .await
23549        .expect("Prepare rename failed");
23550
23551    let mut rename_handler =
23552        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23553            let edit = lsp::TextEdit {
23554                range: lsp::Range {
23555                    start: lsp::Position {
23556                        line: 0,
23557                        character: 7,
23558                    },
23559                    end: lsp::Position {
23560                        line: 0,
23561                        character: 10,
23562                    },
23563                },
23564                new_text: "FooRenamed".to_string(),
23565            };
23566            Ok(Some(lsp::WorkspaceEdit::new(
23567                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
23568            )))
23569        });
23570    let rename_task = cx
23571        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23572        .expect("Confirm rename was not started");
23573    rename_handler.next().await.unwrap();
23574    rename_task.await.expect("Confirm rename failed");
23575    cx.run_until_parked();
23576
23577    // Correct range is renamed, as `surrounding_word` is used to find it.
23578    cx.assert_editor_state(indoc! {"
23579        struct FooRenamedˇ {}
23580    "});
23581}
23582
23583#[gpui::test]
23584async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
23585    init_test(cx, |_| {});
23586    let mut cx = EditorTestContext::new(cx).await;
23587
23588    let language = Arc::new(
23589        Language::new(
23590            LanguageConfig::default(),
23591            Some(tree_sitter_html::LANGUAGE.into()),
23592        )
23593        .with_brackets_query(
23594            r#"
23595            ("<" @open "/>" @close)
23596            ("</" @open ">" @close)
23597            ("<" @open ">" @close)
23598            ("\"" @open "\"" @close)
23599            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
23600        "#,
23601        )
23602        .unwrap(),
23603    );
23604    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23605
23606    cx.set_state(indoc! {"
23607        <span>ˇ</span>
23608    "});
23609    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23610    cx.assert_editor_state(indoc! {"
23611        <span>
23612        ˇ
23613        </span>
23614    "});
23615
23616    cx.set_state(indoc! {"
23617        <span><span></span>ˇ</span>
23618    "});
23619    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23620    cx.assert_editor_state(indoc! {"
23621        <span><span></span>
23622        ˇ</span>
23623    "});
23624
23625    cx.set_state(indoc! {"
23626        <span>ˇ
23627        </span>
23628    "});
23629    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23630    cx.assert_editor_state(indoc! {"
23631        <span>
23632        ˇ
23633        </span>
23634    "});
23635}
23636
23637#[gpui::test(iterations = 10)]
23638async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
23639    init_test(cx, |_| {});
23640
23641    let fs = FakeFs::new(cx.executor());
23642    fs.insert_tree(
23643        path!("/dir"),
23644        json!({
23645            "a.ts": "a",
23646        }),
23647    )
23648    .await;
23649
23650    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
23651    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23652    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23653
23654    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23655    language_registry.add(Arc::new(Language::new(
23656        LanguageConfig {
23657            name: "TypeScript".into(),
23658            matcher: LanguageMatcher {
23659                path_suffixes: vec!["ts".to_string()],
23660                ..Default::default()
23661            },
23662            ..Default::default()
23663        },
23664        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
23665    )));
23666    let mut fake_language_servers = language_registry.register_fake_lsp(
23667        "TypeScript",
23668        FakeLspAdapter {
23669            capabilities: lsp::ServerCapabilities {
23670                code_lens_provider: Some(lsp::CodeLensOptions {
23671                    resolve_provider: Some(true),
23672                }),
23673                execute_command_provider: Some(lsp::ExecuteCommandOptions {
23674                    commands: vec!["_the/command".to_string()],
23675                    ..lsp::ExecuteCommandOptions::default()
23676                }),
23677                ..lsp::ServerCapabilities::default()
23678            },
23679            ..FakeLspAdapter::default()
23680        },
23681    );
23682
23683    let editor = workspace
23684        .update(cx, |workspace, window, cx| {
23685            workspace.open_abs_path(
23686                PathBuf::from(path!("/dir/a.ts")),
23687                OpenOptions::default(),
23688                window,
23689                cx,
23690            )
23691        })
23692        .unwrap()
23693        .await
23694        .unwrap()
23695        .downcast::<Editor>()
23696        .unwrap();
23697    cx.executor().run_until_parked();
23698
23699    let fake_server = fake_language_servers.next().await.unwrap();
23700
23701    let buffer = editor.update(cx, |editor, cx| {
23702        editor
23703            .buffer()
23704            .read(cx)
23705            .as_singleton()
23706            .expect("have opened a single file by path")
23707    });
23708
23709    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
23710    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
23711    drop(buffer_snapshot);
23712    let actions = cx
23713        .update_window(*workspace, |_, window, cx| {
23714            project.code_actions(&buffer, anchor..anchor, window, cx)
23715        })
23716        .unwrap();
23717
23718    fake_server
23719        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23720            Ok(Some(vec![
23721                lsp::CodeLens {
23722                    range: lsp::Range::default(),
23723                    command: Some(lsp::Command {
23724                        title: "Code lens command".to_owned(),
23725                        command: "_the/command".to_owned(),
23726                        arguments: None,
23727                    }),
23728                    data: None,
23729                },
23730                lsp::CodeLens {
23731                    range: lsp::Range::default(),
23732                    command: Some(lsp::Command {
23733                        title: "Command not in capabilities".to_owned(),
23734                        command: "not in capabilities".to_owned(),
23735                        arguments: None,
23736                    }),
23737                    data: None,
23738                },
23739                lsp::CodeLens {
23740                    range: lsp::Range {
23741                        start: lsp::Position {
23742                            line: 1,
23743                            character: 1,
23744                        },
23745                        end: lsp::Position {
23746                            line: 1,
23747                            character: 1,
23748                        },
23749                    },
23750                    command: Some(lsp::Command {
23751                        title: "Command not in range".to_owned(),
23752                        command: "_the/command".to_owned(),
23753                        arguments: None,
23754                    }),
23755                    data: None,
23756                },
23757            ]))
23758        })
23759        .next()
23760        .await;
23761
23762    let actions = actions.await.unwrap();
23763    assert_eq!(
23764        actions.len(),
23765        1,
23766        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
23767    );
23768    let action = actions[0].clone();
23769    let apply = project.update(cx, |project, cx| {
23770        project.apply_code_action(buffer.clone(), action, true, cx)
23771    });
23772
23773    // Resolving the code action does not populate its edits. In absence of
23774    // edits, we must execute the given command.
23775    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
23776        |mut lens, _| async move {
23777            let lens_command = lens.command.as_mut().expect("should have a command");
23778            assert_eq!(lens_command.title, "Code lens command");
23779            lens_command.arguments = Some(vec![json!("the-argument")]);
23780            Ok(lens)
23781        },
23782    );
23783
23784    // While executing the command, the language server sends the editor
23785    // a `workspaceEdit` request.
23786    fake_server
23787        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
23788            let fake = fake_server.clone();
23789            move |params, _| {
23790                assert_eq!(params.command, "_the/command");
23791                let fake = fake.clone();
23792                async move {
23793                    fake.server
23794                        .request::<lsp::request::ApplyWorkspaceEdit>(
23795                            lsp::ApplyWorkspaceEditParams {
23796                                label: None,
23797                                edit: lsp::WorkspaceEdit {
23798                                    changes: Some(
23799                                        [(
23800                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
23801                                            vec![lsp::TextEdit {
23802                                                range: lsp::Range::new(
23803                                                    lsp::Position::new(0, 0),
23804                                                    lsp::Position::new(0, 0),
23805                                                ),
23806                                                new_text: "X".into(),
23807                                            }],
23808                                        )]
23809                                        .into_iter()
23810                                        .collect(),
23811                                    ),
23812                                    ..lsp::WorkspaceEdit::default()
23813                                },
23814                            },
23815                        )
23816                        .await
23817                        .into_response()
23818                        .unwrap();
23819                    Ok(Some(json!(null)))
23820                }
23821            }
23822        })
23823        .next()
23824        .await;
23825
23826    // Applying the code lens command returns a project transaction containing the edits
23827    // sent by the language server in its `workspaceEdit` request.
23828    let transaction = apply.await.unwrap();
23829    assert!(transaction.0.contains_key(&buffer));
23830    buffer.update(cx, |buffer, cx| {
23831        assert_eq!(buffer.text(), "Xa");
23832        buffer.undo(cx);
23833        assert_eq!(buffer.text(), "a");
23834    });
23835
23836    let actions_after_edits = cx
23837        .update_window(*workspace, |_, window, cx| {
23838            project.code_actions(&buffer, anchor..anchor, window, cx)
23839        })
23840        .unwrap()
23841        .await
23842        .unwrap();
23843    assert_eq!(
23844        actions, actions_after_edits,
23845        "For the same selection, same code lens actions should be returned"
23846    );
23847
23848    let _responses =
23849        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23850            panic!("No more code lens requests are expected");
23851        });
23852    editor.update_in(cx, |editor, window, cx| {
23853        editor.select_all(&SelectAll, window, cx);
23854    });
23855    cx.executor().run_until_parked();
23856    let new_actions = cx
23857        .update_window(*workspace, |_, window, cx| {
23858            project.code_actions(&buffer, anchor..anchor, window, cx)
23859        })
23860        .unwrap()
23861        .await
23862        .unwrap();
23863    assert_eq!(
23864        actions, new_actions,
23865        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
23866    );
23867}
23868
23869#[gpui::test]
23870async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
23871    init_test(cx, |_| {});
23872
23873    let fs = FakeFs::new(cx.executor());
23874    let main_text = r#"fn main() {
23875println!("1");
23876println!("2");
23877println!("3");
23878println!("4");
23879println!("5");
23880}"#;
23881    let lib_text = "mod foo {}";
23882    fs.insert_tree(
23883        path!("/a"),
23884        json!({
23885            "lib.rs": lib_text,
23886            "main.rs": main_text,
23887        }),
23888    )
23889    .await;
23890
23891    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23892    let (workspace, cx) =
23893        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23894    let worktree_id = workspace.update(cx, |workspace, cx| {
23895        workspace.project().update(cx, |project, cx| {
23896            project.worktrees(cx).next().unwrap().read(cx).id()
23897        })
23898    });
23899
23900    let expected_ranges = vec![
23901        Point::new(0, 0)..Point::new(0, 0),
23902        Point::new(1, 0)..Point::new(1, 1),
23903        Point::new(2, 0)..Point::new(2, 2),
23904        Point::new(3, 0)..Point::new(3, 3),
23905    ];
23906
23907    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23908    let editor_1 = workspace
23909        .update_in(cx, |workspace, window, cx| {
23910            workspace.open_path(
23911                (worktree_id, rel_path("main.rs")),
23912                Some(pane_1.downgrade()),
23913                true,
23914                window,
23915                cx,
23916            )
23917        })
23918        .unwrap()
23919        .await
23920        .downcast::<Editor>()
23921        .unwrap();
23922    pane_1.update(cx, |pane, cx| {
23923        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23924        open_editor.update(cx, |editor, cx| {
23925            assert_eq!(
23926                editor.display_text(cx),
23927                main_text,
23928                "Original main.rs text on initial open",
23929            );
23930            assert_eq!(
23931                editor
23932                    .selections
23933                    .all::<Point>(&editor.display_snapshot(cx))
23934                    .into_iter()
23935                    .map(|s| s.range())
23936                    .collect::<Vec<_>>(),
23937                vec![Point::zero()..Point::zero()],
23938                "Default selections on initial open",
23939            );
23940        })
23941    });
23942    editor_1.update_in(cx, |editor, window, cx| {
23943        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23944            s.select_ranges(expected_ranges.clone());
23945        });
23946    });
23947
23948    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
23949        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
23950    });
23951    let editor_2 = workspace
23952        .update_in(cx, |workspace, window, cx| {
23953            workspace.open_path(
23954                (worktree_id, rel_path("main.rs")),
23955                Some(pane_2.downgrade()),
23956                true,
23957                window,
23958                cx,
23959            )
23960        })
23961        .unwrap()
23962        .await
23963        .downcast::<Editor>()
23964        .unwrap();
23965    pane_2.update(cx, |pane, cx| {
23966        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23967        open_editor.update(cx, |editor, cx| {
23968            assert_eq!(
23969                editor.display_text(cx),
23970                main_text,
23971                "Original main.rs text on initial open in another panel",
23972            );
23973            assert_eq!(
23974                editor
23975                    .selections
23976                    .all::<Point>(&editor.display_snapshot(cx))
23977                    .into_iter()
23978                    .map(|s| s.range())
23979                    .collect::<Vec<_>>(),
23980                vec![Point::zero()..Point::zero()],
23981                "Default selections on initial open in another panel",
23982            );
23983        })
23984    });
23985
23986    editor_2.update_in(cx, |editor, window, cx| {
23987        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
23988    });
23989
23990    let _other_editor_1 = workspace
23991        .update_in(cx, |workspace, window, cx| {
23992            workspace.open_path(
23993                (worktree_id, rel_path("lib.rs")),
23994                Some(pane_1.downgrade()),
23995                true,
23996                window,
23997                cx,
23998            )
23999        })
24000        .unwrap()
24001        .await
24002        .downcast::<Editor>()
24003        .unwrap();
24004    pane_1
24005        .update_in(cx, |pane, window, cx| {
24006            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
24007        })
24008        .await
24009        .unwrap();
24010    drop(editor_1);
24011    pane_1.update(cx, |pane, cx| {
24012        pane.active_item()
24013            .unwrap()
24014            .downcast::<Editor>()
24015            .unwrap()
24016            .update(cx, |editor, cx| {
24017                assert_eq!(
24018                    editor.display_text(cx),
24019                    lib_text,
24020                    "Other file should be open and active",
24021                );
24022            });
24023        assert_eq!(pane.items().count(), 1, "No other editors should be open");
24024    });
24025
24026    let _other_editor_2 = workspace
24027        .update_in(cx, |workspace, window, cx| {
24028            workspace.open_path(
24029                (worktree_id, rel_path("lib.rs")),
24030                Some(pane_2.downgrade()),
24031                true,
24032                window,
24033                cx,
24034            )
24035        })
24036        .unwrap()
24037        .await
24038        .downcast::<Editor>()
24039        .unwrap();
24040    pane_2
24041        .update_in(cx, |pane, window, cx| {
24042            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
24043        })
24044        .await
24045        .unwrap();
24046    drop(editor_2);
24047    pane_2.update(cx, |pane, cx| {
24048        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24049        open_editor.update(cx, |editor, cx| {
24050            assert_eq!(
24051                editor.display_text(cx),
24052                lib_text,
24053                "Other file should be open and active in another panel too",
24054            );
24055        });
24056        assert_eq!(
24057            pane.items().count(),
24058            1,
24059            "No other editors should be open in another pane",
24060        );
24061    });
24062
24063    let _editor_1_reopened = workspace
24064        .update_in(cx, |workspace, window, cx| {
24065            workspace.open_path(
24066                (worktree_id, rel_path("main.rs")),
24067                Some(pane_1.downgrade()),
24068                true,
24069                window,
24070                cx,
24071            )
24072        })
24073        .unwrap()
24074        .await
24075        .downcast::<Editor>()
24076        .unwrap();
24077    let _editor_2_reopened = workspace
24078        .update_in(cx, |workspace, window, cx| {
24079            workspace.open_path(
24080                (worktree_id, rel_path("main.rs")),
24081                Some(pane_2.downgrade()),
24082                true,
24083                window,
24084                cx,
24085            )
24086        })
24087        .unwrap()
24088        .await
24089        .downcast::<Editor>()
24090        .unwrap();
24091    pane_1.update(cx, |pane, cx| {
24092        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24093        open_editor.update(cx, |editor, cx| {
24094            assert_eq!(
24095                editor.display_text(cx),
24096                main_text,
24097                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
24098            );
24099            assert_eq!(
24100                editor
24101                    .selections
24102                    .all::<Point>(&editor.display_snapshot(cx))
24103                    .into_iter()
24104                    .map(|s| s.range())
24105                    .collect::<Vec<_>>(),
24106                expected_ranges,
24107                "Previous editor in the 1st panel had selections and should get them restored on reopen",
24108            );
24109        })
24110    });
24111    pane_2.update(cx, |pane, cx| {
24112        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24113        open_editor.update(cx, |editor, cx| {
24114            assert_eq!(
24115                editor.display_text(cx),
24116                r#"fn main() {
24117⋯rintln!("1");
24118⋯intln!("2");
24119⋯ntln!("3");
24120println!("4");
24121println!("5");
24122}"#,
24123                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
24124            );
24125            assert_eq!(
24126                editor
24127                    .selections
24128                    .all::<Point>(&editor.display_snapshot(cx))
24129                    .into_iter()
24130                    .map(|s| s.range())
24131                    .collect::<Vec<_>>(),
24132                vec![Point::zero()..Point::zero()],
24133                "Previous editor in the 2nd pane had no selections changed hence should restore none",
24134            );
24135        })
24136    });
24137}
24138
24139#[gpui::test]
24140async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
24141    init_test(cx, |_| {});
24142
24143    let fs = FakeFs::new(cx.executor());
24144    let main_text = r#"fn main() {
24145println!("1");
24146println!("2");
24147println!("3");
24148println!("4");
24149println!("5");
24150}"#;
24151    let lib_text = "mod foo {}";
24152    fs.insert_tree(
24153        path!("/a"),
24154        json!({
24155            "lib.rs": lib_text,
24156            "main.rs": main_text,
24157        }),
24158    )
24159    .await;
24160
24161    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24162    let (workspace, cx) =
24163        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24164    let worktree_id = workspace.update(cx, |workspace, cx| {
24165        workspace.project().update(cx, |project, cx| {
24166            project.worktrees(cx).next().unwrap().read(cx).id()
24167        })
24168    });
24169
24170    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24171    let editor = workspace
24172        .update_in(cx, |workspace, window, cx| {
24173            workspace.open_path(
24174                (worktree_id, rel_path("main.rs")),
24175                Some(pane.downgrade()),
24176                true,
24177                window,
24178                cx,
24179            )
24180        })
24181        .unwrap()
24182        .await
24183        .downcast::<Editor>()
24184        .unwrap();
24185    pane.update(cx, |pane, cx| {
24186        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24187        open_editor.update(cx, |editor, cx| {
24188            assert_eq!(
24189                editor.display_text(cx),
24190                main_text,
24191                "Original main.rs text on initial open",
24192            );
24193        })
24194    });
24195    editor.update_in(cx, |editor, window, cx| {
24196        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
24197    });
24198
24199    cx.update_global(|store: &mut SettingsStore, cx| {
24200        store.update_user_settings(cx, |s| {
24201            s.workspace.restore_on_file_reopen = Some(false);
24202        });
24203    });
24204    editor.update_in(cx, |editor, window, cx| {
24205        editor.fold_ranges(
24206            vec![
24207                Point::new(1, 0)..Point::new(1, 1),
24208                Point::new(2, 0)..Point::new(2, 2),
24209                Point::new(3, 0)..Point::new(3, 3),
24210            ],
24211            false,
24212            window,
24213            cx,
24214        );
24215    });
24216    pane.update_in(cx, |pane, window, cx| {
24217        pane.close_all_items(&CloseAllItems::default(), window, cx)
24218    })
24219    .await
24220    .unwrap();
24221    pane.update(cx, |pane, _| {
24222        assert!(pane.active_item().is_none());
24223    });
24224    cx.update_global(|store: &mut SettingsStore, cx| {
24225        store.update_user_settings(cx, |s| {
24226            s.workspace.restore_on_file_reopen = Some(true);
24227        });
24228    });
24229
24230    let _editor_reopened = workspace
24231        .update_in(cx, |workspace, window, cx| {
24232            workspace.open_path(
24233                (worktree_id, rel_path("main.rs")),
24234                Some(pane.downgrade()),
24235                true,
24236                window,
24237                cx,
24238            )
24239        })
24240        .unwrap()
24241        .await
24242        .downcast::<Editor>()
24243        .unwrap();
24244    pane.update(cx, |pane, cx| {
24245        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24246        open_editor.update(cx, |editor, cx| {
24247            assert_eq!(
24248                editor.display_text(cx),
24249                main_text,
24250                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
24251            );
24252        })
24253    });
24254}
24255
24256#[gpui::test]
24257async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
24258    struct EmptyModalView {
24259        focus_handle: gpui::FocusHandle,
24260    }
24261    impl EventEmitter<DismissEvent> for EmptyModalView {}
24262    impl Render for EmptyModalView {
24263        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
24264            div()
24265        }
24266    }
24267    impl Focusable for EmptyModalView {
24268        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
24269            self.focus_handle.clone()
24270        }
24271    }
24272    impl workspace::ModalView for EmptyModalView {}
24273    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
24274        EmptyModalView {
24275            focus_handle: cx.focus_handle(),
24276        }
24277    }
24278
24279    init_test(cx, |_| {});
24280
24281    let fs = FakeFs::new(cx.executor());
24282    let project = Project::test(fs, [], cx).await;
24283    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24284    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
24285    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24286    let editor = cx.new_window_entity(|window, cx| {
24287        Editor::new(
24288            EditorMode::full(),
24289            buffer,
24290            Some(project.clone()),
24291            window,
24292            cx,
24293        )
24294    });
24295    workspace
24296        .update(cx, |workspace, window, cx| {
24297            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
24298        })
24299        .unwrap();
24300    editor.update_in(cx, |editor, window, cx| {
24301        editor.open_context_menu(&OpenContextMenu, window, cx);
24302        assert!(editor.mouse_context_menu.is_some());
24303    });
24304    workspace
24305        .update(cx, |workspace, window, cx| {
24306            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
24307        })
24308        .unwrap();
24309    cx.read(|cx| {
24310        assert!(editor.read(cx).mouse_context_menu.is_none());
24311    });
24312}
24313
24314fn set_linked_edit_ranges(
24315    opening: (Point, Point),
24316    closing: (Point, Point),
24317    editor: &mut Editor,
24318    cx: &mut Context<Editor>,
24319) {
24320    let Some((buffer, _)) = editor
24321        .buffer
24322        .read(cx)
24323        .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
24324    else {
24325        panic!("Failed to get buffer for selection position");
24326    };
24327    let buffer = buffer.read(cx);
24328    let buffer_id = buffer.remote_id();
24329    let opening_range = buffer.anchor_before(opening.0)..buffer.anchor_after(opening.1);
24330    let closing_range = buffer.anchor_before(closing.0)..buffer.anchor_after(closing.1);
24331    let mut linked_ranges = HashMap::default();
24332    linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
24333    editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
24334}
24335
24336#[gpui::test]
24337async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
24338    init_test(cx, |_| {});
24339
24340    let fs = FakeFs::new(cx.executor());
24341    fs.insert_file(path!("/file.html"), Default::default())
24342        .await;
24343
24344    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
24345
24346    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24347    let html_language = Arc::new(Language::new(
24348        LanguageConfig {
24349            name: "HTML".into(),
24350            matcher: LanguageMatcher {
24351                path_suffixes: vec!["html".to_string()],
24352                ..LanguageMatcher::default()
24353            },
24354            brackets: BracketPairConfig {
24355                pairs: vec![BracketPair {
24356                    start: "<".into(),
24357                    end: ">".into(),
24358                    close: true,
24359                    ..Default::default()
24360                }],
24361                ..Default::default()
24362            },
24363            ..Default::default()
24364        },
24365        Some(tree_sitter_html::LANGUAGE.into()),
24366    ));
24367    language_registry.add(html_language);
24368    let mut fake_servers = language_registry.register_fake_lsp(
24369        "HTML",
24370        FakeLspAdapter {
24371            capabilities: lsp::ServerCapabilities {
24372                completion_provider: Some(lsp::CompletionOptions {
24373                    resolve_provider: Some(true),
24374                    ..Default::default()
24375                }),
24376                ..Default::default()
24377            },
24378            ..Default::default()
24379        },
24380    );
24381
24382    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24383    let cx = &mut VisualTestContext::from_window(*workspace, cx);
24384
24385    let worktree_id = workspace
24386        .update(cx, |workspace, _window, cx| {
24387            workspace.project().update(cx, |project, cx| {
24388                project.worktrees(cx).next().unwrap().read(cx).id()
24389            })
24390        })
24391        .unwrap();
24392    project
24393        .update(cx, |project, cx| {
24394            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
24395        })
24396        .await
24397        .unwrap();
24398    let editor = workspace
24399        .update(cx, |workspace, window, cx| {
24400            workspace.open_path((worktree_id, rel_path("file.html")), None, true, window, cx)
24401        })
24402        .unwrap()
24403        .await
24404        .unwrap()
24405        .downcast::<Editor>()
24406        .unwrap();
24407
24408    let fake_server = fake_servers.next().await.unwrap();
24409    editor.update_in(cx, |editor, window, cx| {
24410        editor.set_text("<ad></ad>", window, cx);
24411        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24412            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
24413        });
24414        set_linked_edit_ranges(
24415            (Point::new(0, 1), Point::new(0, 3)),
24416            (Point::new(0, 6), Point::new(0, 8)),
24417            editor,
24418            cx,
24419        );
24420    });
24421    let mut completion_handle =
24422        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
24423            Ok(Some(lsp::CompletionResponse::Array(vec![
24424                lsp::CompletionItem {
24425                    label: "head".to_string(),
24426                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
24427                        lsp::InsertReplaceEdit {
24428                            new_text: "head".to_string(),
24429                            insert: lsp::Range::new(
24430                                lsp::Position::new(0, 1),
24431                                lsp::Position::new(0, 3),
24432                            ),
24433                            replace: lsp::Range::new(
24434                                lsp::Position::new(0, 1),
24435                                lsp::Position::new(0, 3),
24436                            ),
24437                        },
24438                    )),
24439                    ..Default::default()
24440                },
24441            ])))
24442        });
24443    editor.update_in(cx, |editor, window, cx| {
24444        editor.show_completions(&ShowCompletions, window, cx);
24445    });
24446    cx.run_until_parked();
24447    completion_handle.next().await.unwrap();
24448    editor.update(cx, |editor, _| {
24449        assert!(
24450            editor.context_menu_visible(),
24451            "Completion menu should be visible"
24452        );
24453    });
24454    editor.update_in(cx, |editor, window, cx| {
24455        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
24456    });
24457    cx.executor().run_until_parked();
24458    editor.update(cx, |editor, cx| {
24459        assert_eq!(editor.text(cx), "<head></head>");
24460    });
24461}
24462
24463#[gpui::test]
24464async fn test_linked_edits_on_typing_punctuation(cx: &mut TestAppContext) {
24465    init_test(cx, |_| {});
24466
24467    let mut cx = EditorTestContext::new(cx).await;
24468    let language = Arc::new(Language::new(
24469        LanguageConfig {
24470            name: "TSX".into(),
24471            matcher: LanguageMatcher {
24472                path_suffixes: vec!["tsx".to_string()],
24473                ..LanguageMatcher::default()
24474            },
24475            brackets: BracketPairConfig {
24476                pairs: vec![BracketPair {
24477                    start: "<".into(),
24478                    end: ">".into(),
24479                    close: true,
24480                    ..Default::default()
24481                }],
24482                ..Default::default()
24483            },
24484            linked_edit_characters: HashSet::from_iter(['.']),
24485            ..Default::default()
24486        },
24487        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
24488    ));
24489    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24490
24491    // Test typing > does not extend linked pair
24492    cx.set_state("<divˇ<div></div>");
24493    cx.update_editor(|editor, _, cx| {
24494        set_linked_edit_ranges(
24495            (Point::new(0, 1), Point::new(0, 4)),
24496            (Point::new(0, 11), Point::new(0, 14)),
24497            editor,
24498            cx,
24499        );
24500    });
24501    cx.update_editor(|editor, window, cx| {
24502        editor.handle_input(">", window, cx);
24503    });
24504    cx.assert_editor_state("<div>ˇ<div></div>");
24505
24506    // Test typing . do extend linked pair
24507    cx.set_state("<Animatedˇ></Animated>");
24508    cx.update_editor(|editor, _, cx| {
24509        set_linked_edit_ranges(
24510            (Point::new(0, 1), Point::new(0, 9)),
24511            (Point::new(0, 12), Point::new(0, 20)),
24512            editor,
24513            cx,
24514        );
24515    });
24516    cx.update_editor(|editor, window, cx| {
24517        editor.handle_input(".", window, cx);
24518    });
24519    cx.assert_editor_state("<Animated.ˇ></Animated.>");
24520    cx.update_editor(|editor, _, cx| {
24521        set_linked_edit_ranges(
24522            (Point::new(0, 1), Point::new(0, 10)),
24523            (Point::new(0, 13), Point::new(0, 21)),
24524            editor,
24525            cx,
24526        );
24527    });
24528    cx.update_editor(|editor, window, cx| {
24529        editor.handle_input("V", window, cx);
24530    });
24531    cx.assert_editor_state("<Animated.Vˇ></Animated.V>");
24532}
24533
24534#[gpui::test]
24535async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
24536    init_test(cx, |_| {});
24537
24538    let fs = FakeFs::new(cx.executor());
24539    fs.insert_tree(
24540        path!("/root"),
24541        json!({
24542            "a": {
24543                "main.rs": "fn main() {}",
24544            },
24545            "foo": {
24546                "bar": {
24547                    "external_file.rs": "pub mod external {}",
24548                }
24549            }
24550        }),
24551    )
24552    .await;
24553
24554    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
24555    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24556    language_registry.add(rust_lang());
24557    let _fake_servers = language_registry.register_fake_lsp(
24558        "Rust",
24559        FakeLspAdapter {
24560            ..FakeLspAdapter::default()
24561        },
24562    );
24563    let (workspace, cx) =
24564        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24565    let worktree_id = workspace.update(cx, |workspace, cx| {
24566        workspace.project().update(cx, |project, cx| {
24567            project.worktrees(cx).next().unwrap().read(cx).id()
24568        })
24569    });
24570
24571    let assert_language_servers_count =
24572        |expected: usize, context: &str, cx: &mut VisualTestContext| {
24573            project.update(cx, |project, cx| {
24574                let current = project
24575                    .lsp_store()
24576                    .read(cx)
24577                    .as_local()
24578                    .unwrap()
24579                    .language_servers
24580                    .len();
24581                assert_eq!(expected, current, "{context}");
24582            });
24583        };
24584
24585    assert_language_servers_count(
24586        0,
24587        "No servers should be running before any file is open",
24588        cx,
24589    );
24590    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24591    let main_editor = workspace
24592        .update_in(cx, |workspace, window, cx| {
24593            workspace.open_path(
24594                (worktree_id, rel_path("main.rs")),
24595                Some(pane.downgrade()),
24596                true,
24597                window,
24598                cx,
24599            )
24600        })
24601        .unwrap()
24602        .await
24603        .downcast::<Editor>()
24604        .unwrap();
24605    pane.update(cx, |pane, cx| {
24606        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24607        open_editor.update(cx, |editor, cx| {
24608            assert_eq!(
24609                editor.display_text(cx),
24610                "fn main() {}",
24611                "Original main.rs text on initial open",
24612            );
24613        });
24614        assert_eq!(open_editor, main_editor);
24615    });
24616    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
24617
24618    let external_editor = workspace
24619        .update_in(cx, |workspace, window, cx| {
24620            workspace.open_abs_path(
24621                PathBuf::from("/root/foo/bar/external_file.rs"),
24622                OpenOptions::default(),
24623                window,
24624                cx,
24625            )
24626        })
24627        .await
24628        .expect("opening external file")
24629        .downcast::<Editor>()
24630        .expect("downcasted external file's open element to editor");
24631    pane.update(cx, |pane, cx| {
24632        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24633        open_editor.update(cx, |editor, cx| {
24634            assert_eq!(
24635                editor.display_text(cx),
24636                "pub mod external {}",
24637                "External file is open now",
24638            );
24639        });
24640        assert_eq!(open_editor, external_editor);
24641    });
24642    assert_language_servers_count(
24643        1,
24644        "Second, external, *.rs file should join the existing server",
24645        cx,
24646    );
24647
24648    pane.update_in(cx, |pane, window, cx| {
24649        pane.close_active_item(&CloseActiveItem::default(), window, cx)
24650    })
24651    .await
24652    .unwrap();
24653    pane.update_in(cx, |pane, window, cx| {
24654        pane.navigate_backward(&Default::default(), window, cx);
24655    });
24656    cx.run_until_parked();
24657    pane.update(cx, |pane, cx| {
24658        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24659        open_editor.update(cx, |editor, cx| {
24660            assert_eq!(
24661                editor.display_text(cx),
24662                "pub mod external {}",
24663                "External file is open now",
24664            );
24665        });
24666    });
24667    assert_language_servers_count(
24668        1,
24669        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
24670        cx,
24671    );
24672
24673    cx.update(|_, cx| {
24674        workspace::reload(cx);
24675    });
24676    assert_language_servers_count(
24677        1,
24678        "After reloading the worktree with local and external files opened, only one project should be started",
24679        cx,
24680    );
24681}
24682
24683#[gpui::test]
24684async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
24685    init_test(cx, |_| {});
24686
24687    let mut cx = EditorTestContext::new(cx).await;
24688    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24689    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24690
24691    // test cursor move to start of each line on tab
24692    // for `if`, `elif`, `else`, `while`, `with` and `for`
24693    cx.set_state(indoc! {"
24694        def main():
24695        ˇ    for item in items:
24696        ˇ        while item.active:
24697        ˇ            if item.value > 10:
24698        ˇ                continue
24699        ˇ            elif item.value < 0:
24700        ˇ                break
24701        ˇ            else:
24702        ˇ                with item.context() as ctx:
24703        ˇ                    yield count
24704        ˇ        else:
24705        ˇ            log('while else')
24706        ˇ    else:
24707        ˇ        log('for else')
24708    "});
24709    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24710    cx.assert_editor_state(indoc! {"
24711        def main():
24712            ˇfor item in items:
24713                ˇwhile item.active:
24714                    ˇif item.value > 10:
24715                        ˇcontinue
24716                    ˇelif item.value < 0:
24717                        ˇbreak
24718                    ˇelse:
24719                        ˇwith item.context() as ctx:
24720                            ˇyield count
24721                ˇelse:
24722                    ˇlog('while else')
24723            ˇelse:
24724                ˇlog('for else')
24725    "});
24726    // test relative indent is preserved when tab
24727    // for `if`, `elif`, `else`, `while`, `with` and `for`
24728    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24729    cx.assert_editor_state(indoc! {"
24730        def main():
24731                ˇfor item in items:
24732                    ˇwhile item.active:
24733                        ˇif item.value > 10:
24734                            ˇcontinue
24735                        ˇelif item.value < 0:
24736                            ˇbreak
24737                        ˇelse:
24738                            ˇwith item.context() as ctx:
24739                                ˇyield count
24740                    ˇelse:
24741                        ˇlog('while else')
24742                ˇelse:
24743                    ˇlog('for else')
24744    "});
24745
24746    // test cursor move to start of each line on tab
24747    // for `try`, `except`, `else`, `finally`, `match` and `def`
24748    cx.set_state(indoc! {"
24749        def main():
24750        ˇ    try:
24751        ˇ        fetch()
24752        ˇ    except ValueError:
24753        ˇ        handle_error()
24754        ˇ    else:
24755        ˇ        match value:
24756        ˇ            case _:
24757        ˇ    finally:
24758        ˇ        def status():
24759        ˇ            return 0
24760    "});
24761    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24762    cx.assert_editor_state(indoc! {"
24763        def main():
24764            ˇtry:
24765                ˇfetch()
24766            ˇexcept ValueError:
24767                ˇhandle_error()
24768            ˇelse:
24769                ˇmatch value:
24770                    ˇcase _:
24771            ˇfinally:
24772                ˇdef status():
24773                    ˇreturn 0
24774    "});
24775    // test relative indent is preserved when tab
24776    // for `try`, `except`, `else`, `finally`, `match` and `def`
24777    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24778    cx.assert_editor_state(indoc! {"
24779        def main():
24780                ˇtry:
24781                    ˇfetch()
24782                ˇexcept ValueError:
24783                    ˇhandle_error()
24784                ˇelse:
24785                    ˇmatch value:
24786                        ˇcase _:
24787                ˇfinally:
24788                    ˇdef status():
24789                        ˇreturn 0
24790    "});
24791}
24792
24793#[gpui::test]
24794async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
24795    init_test(cx, |_| {});
24796
24797    let mut cx = EditorTestContext::new(cx).await;
24798    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24799    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24800
24801    // test `else` auto outdents when typed inside `if` block
24802    cx.set_state(indoc! {"
24803        def main():
24804            if i == 2:
24805                return
24806                ˇ
24807    "});
24808    cx.update_editor(|editor, window, cx| {
24809        editor.handle_input("else:", window, cx);
24810    });
24811    cx.assert_editor_state(indoc! {"
24812        def main():
24813            if i == 2:
24814                return
24815            else:ˇ
24816    "});
24817
24818    // test `except` auto outdents when typed inside `try` block
24819    cx.set_state(indoc! {"
24820        def main():
24821            try:
24822                i = 2
24823                ˇ
24824    "});
24825    cx.update_editor(|editor, window, cx| {
24826        editor.handle_input("except:", window, cx);
24827    });
24828    cx.assert_editor_state(indoc! {"
24829        def main():
24830            try:
24831                i = 2
24832            except:ˇ
24833    "});
24834
24835    // test `else` auto outdents when typed inside `except` block
24836    cx.set_state(indoc! {"
24837        def main():
24838            try:
24839                i = 2
24840            except:
24841                j = 2
24842                ˇ
24843    "});
24844    cx.update_editor(|editor, window, cx| {
24845        editor.handle_input("else:", window, cx);
24846    });
24847    cx.assert_editor_state(indoc! {"
24848        def main():
24849            try:
24850                i = 2
24851            except:
24852                j = 2
24853            else:ˇ
24854    "});
24855
24856    // test `finally` auto outdents when typed inside `else` block
24857    cx.set_state(indoc! {"
24858        def main():
24859            try:
24860                i = 2
24861            except:
24862                j = 2
24863            else:
24864                k = 2
24865                ˇ
24866    "});
24867    cx.update_editor(|editor, window, cx| {
24868        editor.handle_input("finally:", window, cx);
24869    });
24870    cx.assert_editor_state(indoc! {"
24871        def main():
24872            try:
24873                i = 2
24874            except:
24875                j = 2
24876            else:
24877                k = 2
24878            finally:ˇ
24879    "});
24880
24881    // test `else` does not outdents when typed inside `except` block right after for block
24882    cx.set_state(indoc! {"
24883        def main():
24884            try:
24885                i = 2
24886            except:
24887                for i in range(n):
24888                    pass
24889                ˇ
24890    "});
24891    cx.update_editor(|editor, window, cx| {
24892        editor.handle_input("else:", window, cx);
24893    });
24894    cx.assert_editor_state(indoc! {"
24895        def main():
24896            try:
24897                i = 2
24898            except:
24899                for i in range(n):
24900                    pass
24901                else:ˇ
24902    "});
24903
24904    // test `finally` auto outdents when typed inside `else` block right after for block
24905    cx.set_state(indoc! {"
24906        def main():
24907            try:
24908                i = 2
24909            except:
24910                j = 2
24911            else:
24912                for i in range(n):
24913                    pass
24914                ˇ
24915    "});
24916    cx.update_editor(|editor, window, cx| {
24917        editor.handle_input("finally:", window, cx);
24918    });
24919    cx.assert_editor_state(indoc! {"
24920        def main():
24921            try:
24922                i = 2
24923            except:
24924                j = 2
24925            else:
24926                for i in range(n):
24927                    pass
24928            finally:ˇ
24929    "});
24930
24931    // test `except` outdents to inner "try" block
24932    cx.set_state(indoc! {"
24933        def main():
24934            try:
24935                i = 2
24936                if i == 2:
24937                    try:
24938                        i = 3
24939                        ˇ
24940    "});
24941    cx.update_editor(|editor, window, cx| {
24942        editor.handle_input("except:", window, cx);
24943    });
24944    cx.assert_editor_state(indoc! {"
24945        def main():
24946            try:
24947                i = 2
24948                if i == 2:
24949                    try:
24950                        i = 3
24951                    except:ˇ
24952    "});
24953
24954    // test `except` outdents to outer "try" block
24955    cx.set_state(indoc! {"
24956        def main():
24957            try:
24958                i = 2
24959                if i == 2:
24960                    try:
24961                        i = 3
24962                ˇ
24963    "});
24964    cx.update_editor(|editor, window, cx| {
24965        editor.handle_input("except:", window, cx);
24966    });
24967    cx.assert_editor_state(indoc! {"
24968        def main():
24969            try:
24970                i = 2
24971                if i == 2:
24972                    try:
24973                        i = 3
24974            except:ˇ
24975    "});
24976
24977    // test `else` stays at correct indent when typed after `for` block
24978    cx.set_state(indoc! {"
24979        def main():
24980            for i in range(10):
24981                if i == 3:
24982                    break
24983            ˇ
24984    "});
24985    cx.update_editor(|editor, window, cx| {
24986        editor.handle_input("else:", window, cx);
24987    });
24988    cx.assert_editor_state(indoc! {"
24989        def main():
24990            for i in range(10):
24991                if i == 3:
24992                    break
24993            else:ˇ
24994    "});
24995
24996    // test does not outdent on typing after line with square brackets
24997    cx.set_state(indoc! {"
24998        def f() -> list[str]:
24999            ˇ
25000    "});
25001    cx.update_editor(|editor, window, cx| {
25002        editor.handle_input("a", window, cx);
25003    });
25004    cx.assert_editor_state(indoc! {"
25005        def f() -> list[str]:
2500625007    "});
25008
25009    // test does not outdent on typing : after case keyword
25010    cx.set_state(indoc! {"
25011        match 1:
25012            caseˇ
25013    "});
25014    cx.update_editor(|editor, window, cx| {
25015        editor.handle_input(":", window, cx);
25016    });
25017    cx.assert_editor_state(indoc! {"
25018        match 1:
25019            case:ˇ
25020    "});
25021}
25022
25023#[gpui::test]
25024async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
25025    init_test(cx, |_| {});
25026    update_test_language_settings(cx, |settings| {
25027        settings.defaults.extend_comment_on_newline = Some(false);
25028    });
25029    let mut cx = EditorTestContext::new(cx).await;
25030    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
25031    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25032
25033    // test correct indent after newline on comment
25034    cx.set_state(indoc! {"
25035        # COMMENT:ˇ
25036    "});
25037    cx.update_editor(|editor, window, cx| {
25038        editor.newline(&Newline, window, cx);
25039    });
25040    cx.assert_editor_state(indoc! {"
25041        # COMMENT:
25042        ˇ
25043    "});
25044
25045    // test correct indent after newline in brackets
25046    cx.set_state(indoc! {"
25047        {ˇ}
25048    "});
25049    cx.update_editor(|editor, window, cx| {
25050        editor.newline(&Newline, window, cx);
25051    });
25052    cx.run_until_parked();
25053    cx.assert_editor_state(indoc! {"
25054        {
25055            ˇ
25056        }
25057    "});
25058
25059    cx.set_state(indoc! {"
25060        (ˇ)
25061    "});
25062    cx.update_editor(|editor, window, cx| {
25063        editor.newline(&Newline, window, cx);
25064    });
25065    cx.run_until_parked();
25066    cx.assert_editor_state(indoc! {"
25067        (
25068            ˇ
25069        )
25070    "});
25071
25072    // do not indent after empty lists or dictionaries
25073    cx.set_state(indoc! {"
25074        a = []ˇ
25075    "});
25076    cx.update_editor(|editor, window, cx| {
25077        editor.newline(&Newline, window, cx);
25078    });
25079    cx.run_until_parked();
25080    cx.assert_editor_state(indoc! {"
25081        a = []
25082        ˇ
25083    "});
25084}
25085
25086#[gpui::test]
25087async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
25088    init_test(cx, |_| {});
25089
25090    let mut cx = EditorTestContext::new(cx).await;
25091    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25092    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25093
25094    // test cursor move to start of each line on tab
25095    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
25096    cx.set_state(indoc! {"
25097        function main() {
25098        ˇ    for item in $items; do
25099        ˇ        while [ -n \"$item\" ]; do
25100        ˇ            if [ \"$value\" -gt 10 ]; then
25101        ˇ                continue
25102        ˇ            elif [ \"$value\" -lt 0 ]; then
25103        ˇ                break
25104        ˇ            else
25105        ˇ                echo \"$item\"
25106        ˇ            fi
25107        ˇ        done
25108        ˇ    done
25109        ˇ}
25110    "});
25111    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25112    cx.assert_editor_state(indoc! {"
25113        function main() {
25114            ˇfor item in $items; do
25115                ˇwhile [ -n \"$item\" ]; do
25116                    ˇif [ \"$value\" -gt 10 ]; then
25117                        ˇcontinue
25118                    ˇelif [ \"$value\" -lt 0 ]; then
25119                        ˇbreak
25120                    ˇelse
25121                        ˇecho \"$item\"
25122                    ˇfi
25123                ˇdone
25124            ˇdone
25125        ˇ}
25126    "});
25127    // test relative indent is preserved when tab
25128    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25129    cx.assert_editor_state(indoc! {"
25130        function main() {
25131                ˇfor item in $items; do
25132                    ˇwhile [ -n \"$item\" ]; do
25133                        ˇif [ \"$value\" -gt 10 ]; then
25134                            ˇcontinue
25135                        ˇelif [ \"$value\" -lt 0 ]; then
25136                            ˇbreak
25137                        ˇelse
25138                            ˇecho \"$item\"
25139                        ˇfi
25140                    ˇdone
25141                ˇdone
25142            ˇ}
25143    "});
25144
25145    // test cursor move to start of each line on tab
25146    // for `case` statement with patterns
25147    cx.set_state(indoc! {"
25148        function handle() {
25149        ˇ    case \"$1\" in
25150        ˇ        start)
25151        ˇ            echo \"a\"
25152        ˇ            ;;
25153        ˇ        stop)
25154        ˇ            echo \"b\"
25155        ˇ            ;;
25156        ˇ        *)
25157        ˇ            echo \"c\"
25158        ˇ            ;;
25159        ˇ    esac
25160        ˇ}
25161    "});
25162    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25163    cx.assert_editor_state(indoc! {"
25164        function handle() {
25165            ˇcase \"$1\" in
25166                ˇstart)
25167                    ˇecho \"a\"
25168                    ˇ;;
25169                ˇstop)
25170                    ˇecho \"b\"
25171                    ˇ;;
25172                ˇ*)
25173                    ˇecho \"c\"
25174                    ˇ;;
25175            ˇesac
25176        ˇ}
25177    "});
25178}
25179
25180#[gpui::test]
25181async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
25182    init_test(cx, |_| {});
25183
25184    let mut cx = EditorTestContext::new(cx).await;
25185    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25186    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25187
25188    // test indents on comment insert
25189    cx.set_state(indoc! {"
25190        function main() {
25191        ˇ    for item in $items; do
25192        ˇ        while [ -n \"$item\" ]; do
25193        ˇ            if [ \"$value\" -gt 10 ]; then
25194        ˇ                continue
25195        ˇ            elif [ \"$value\" -lt 0 ]; then
25196        ˇ                break
25197        ˇ            else
25198        ˇ                echo \"$item\"
25199        ˇ            fi
25200        ˇ        done
25201        ˇ    done
25202        ˇ}
25203    "});
25204    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
25205    cx.assert_editor_state(indoc! {"
25206        function main() {
25207        #ˇ    for item in $items; do
25208        #ˇ        while [ -n \"$item\" ]; do
25209        #ˇ            if [ \"$value\" -gt 10 ]; then
25210        #ˇ                continue
25211        #ˇ            elif [ \"$value\" -lt 0 ]; then
25212        #ˇ                break
25213        #ˇ            else
25214        #ˇ                echo \"$item\"
25215        #ˇ            fi
25216        #ˇ        done
25217        #ˇ    done
25218        #ˇ}
25219    "});
25220}
25221
25222#[gpui::test]
25223async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
25224    init_test(cx, |_| {});
25225
25226    let mut cx = EditorTestContext::new(cx).await;
25227    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25228    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25229
25230    // test `else` auto outdents when typed inside `if` block
25231    cx.set_state(indoc! {"
25232        if [ \"$1\" = \"test\" ]; then
25233            echo \"foo bar\"
25234            ˇ
25235    "});
25236    cx.update_editor(|editor, window, cx| {
25237        editor.handle_input("else", window, cx);
25238    });
25239    cx.assert_editor_state(indoc! {"
25240        if [ \"$1\" = \"test\" ]; then
25241            echo \"foo bar\"
25242        elseˇ
25243    "});
25244
25245    // test `elif` auto outdents when typed inside `if` block
25246    cx.set_state(indoc! {"
25247        if [ \"$1\" = \"test\" ]; then
25248            echo \"foo bar\"
25249            ˇ
25250    "});
25251    cx.update_editor(|editor, window, cx| {
25252        editor.handle_input("elif", window, cx);
25253    });
25254    cx.assert_editor_state(indoc! {"
25255        if [ \"$1\" = \"test\" ]; then
25256            echo \"foo bar\"
25257        elifˇ
25258    "});
25259
25260    // test `fi` auto outdents when typed inside `else` block
25261    cx.set_state(indoc! {"
25262        if [ \"$1\" = \"test\" ]; then
25263            echo \"foo bar\"
25264        else
25265            echo \"bar baz\"
25266            ˇ
25267    "});
25268    cx.update_editor(|editor, window, cx| {
25269        editor.handle_input("fi", window, cx);
25270    });
25271    cx.assert_editor_state(indoc! {"
25272        if [ \"$1\" = \"test\" ]; then
25273            echo \"foo bar\"
25274        else
25275            echo \"bar baz\"
25276        fiˇ
25277    "});
25278
25279    // test `done` auto outdents when typed inside `while` block
25280    cx.set_state(indoc! {"
25281        while read line; do
25282            echo \"$line\"
25283            ˇ
25284    "});
25285    cx.update_editor(|editor, window, cx| {
25286        editor.handle_input("done", window, cx);
25287    });
25288    cx.assert_editor_state(indoc! {"
25289        while read line; do
25290            echo \"$line\"
25291        doneˇ
25292    "});
25293
25294    // test `done` auto outdents when typed inside `for` block
25295    cx.set_state(indoc! {"
25296        for file in *.txt; do
25297            cat \"$file\"
25298            ˇ
25299    "});
25300    cx.update_editor(|editor, window, cx| {
25301        editor.handle_input("done", window, cx);
25302    });
25303    cx.assert_editor_state(indoc! {"
25304        for file in *.txt; do
25305            cat \"$file\"
25306        doneˇ
25307    "});
25308
25309    // test `esac` auto outdents when typed inside `case` block
25310    cx.set_state(indoc! {"
25311        case \"$1\" in
25312            start)
25313                echo \"foo bar\"
25314                ;;
25315            stop)
25316                echo \"bar baz\"
25317                ;;
25318            ˇ
25319    "});
25320    cx.update_editor(|editor, window, cx| {
25321        editor.handle_input("esac", window, cx);
25322    });
25323    cx.assert_editor_state(indoc! {"
25324        case \"$1\" in
25325            start)
25326                echo \"foo bar\"
25327                ;;
25328            stop)
25329                echo \"bar baz\"
25330                ;;
25331        esacˇ
25332    "});
25333
25334    // test `*)` auto outdents when typed inside `case` block
25335    cx.set_state(indoc! {"
25336        case \"$1\" in
25337            start)
25338                echo \"foo bar\"
25339                ;;
25340                ˇ
25341    "});
25342    cx.update_editor(|editor, window, cx| {
25343        editor.handle_input("*)", window, cx);
25344    });
25345    cx.assert_editor_state(indoc! {"
25346        case \"$1\" in
25347            start)
25348                echo \"foo bar\"
25349                ;;
25350            *)ˇ
25351    "});
25352
25353    // test `fi` outdents to correct level with nested if blocks
25354    cx.set_state(indoc! {"
25355        if [ \"$1\" = \"test\" ]; then
25356            echo \"outer if\"
25357            if [ \"$2\" = \"debug\" ]; then
25358                echo \"inner if\"
25359                ˇ
25360    "});
25361    cx.update_editor(|editor, window, cx| {
25362        editor.handle_input("fi", window, cx);
25363    });
25364    cx.assert_editor_state(indoc! {"
25365        if [ \"$1\" = \"test\" ]; then
25366            echo \"outer if\"
25367            if [ \"$2\" = \"debug\" ]; then
25368                echo \"inner if\"
25369            fiˇ
25370    "});
25371}
25372
25373#[gpui::test]
25374async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
25375    init_test(cx, |_| {});
25376    update_test_language_settings(cx, |settings| {
25377        settings.defaults.extend_comment_on_newline = Some(false);
25378    });
25379    let mut cx = EditorTestContext::new(cx).await;
25380    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25381    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25382
25383    // test correct indent after newline on comment
25384    cx.set_state(indoc! {"
25385        # COMMENT:ˇ
25386    "});
25387    cx.update_editor(|editor, window, cx| {
25388        editor.newline(&Newline, window, cx);
25389    });
25390    cx.assert_editor_state(indoc! {"
25391        # COMMENT:
25392        ˇ
25393    "});
25394
25395    // test correct indent after newline after `then`
25396    cx.set_state(indoc! {"
25397
25398        if [ \"$1\" = \"test\" ]; thenˇ
25399    "});
25400    cx.update_editor(|editor, window, cx| {
25401        editor.newline(&Newline, window, cx);
25402    });
25403    cx.run_until_parked();
25404    cx.assert_editor_state(indoc! {"
25405
25406        if [ \"$1\" = \"test\" ]; then
25407            ˇ
25408    "});
25409
25410    // test correct indent after newline after `else`
25411    cx.set_state(indoc! {"
25412        if [ \"$1\" = \"test\" ]; then
25413        elseˇ
25414    "});
25415    cx.update_editor(|editor, window, cx| {
25416        editor.newline(&Newline, window, cx);
25417    });
25418    cx.run_until_parked();
25419    cx.assert_editor_state(indoc! {"
25420        if [ \"$1\" = \"test\" ]; then
25421        else
25422            ˇ
25423    "});
25424
25425    // test correct indent after newline after `elif`
25426    cx.set_state(indoc! {"
25427        if [ \"$1\" = \"test\" ]; then
25428        elifˇ
25429    "});
25430    cx.update_editor(|editor, window, cx| {
25431        editor.newline(&Newline, window, cx);
25432    });
25433    cx.run_until_parked();
25434    cx.assert_editor_state(indoc! {"
25435        if [ \"$1\" = \"test\" ]; then
25436        elif
25437            ˇ
25438    "});
25439
25440    // test correct indent after newline after `do`
25441    cx.set_state(indoc! {"
25442        for file in *.txt; doˇ
25443    "});
25444    cx.update_editor(|editor, window, cx| {
25445        editor.newline(&Newline, window, cx);
25446    });
25447    cx.run_until_parked();
25448    cx.assert_editor_state(indoc! {"
25449        for file in *.txt; do
25450            ˇ
25451    "});
25452
25453    // test correct indent after newline after case pattern
25454    cx.set_state(indoc! {"
25455        case \"$1\" in
25456            start)ˇ
25457    "});
25458    cx.update_editor(|editor, window, cx| {
25459        editor.newline(&Newline, window, cx);
25460    });
25461    cx.run_until_parked();
25462    cx.assert_editor_state(indoc! {"
25463        case \"$1\" in
25464            start)
25465                ˇ
25466    "});
25467
25468    // test correct indent after newline after case pattern
25469    cx.set_state(indoc! {"
25470        case \"$1\" in
25471            start)
25472                ;;
25473            *)ˇ
25474    "});
25475    cx.update_editor(|editor, window, cx| {
25476        editor.newline(&Newline, window, cx);
25477    });
25478    cx.run_until_parked();
25479    cx.assert_editor_state(indoc! {"
25480        case \"$1\" in
25481            start)
25482                ;;
25483            *)
25484                ˇ
25485    "});
25486
25487    // test correct indent after newline after function opening brace
25488    cx.set_state(indoc! {"
25489        function test() {ˇ}
25490    "});
25491    cx.update_editor(|editor, window, cx| {
25492        editor.newline(&Newline, window, cx);
25493    });
25494    cx.run_until_parked();
25495    cx.assert_editor_state(indoc! {"
25496        function test() {
25497            ˇ
25498        }
25499    "});
25500
25501    // test no extra indent after semicolon on same line
25502    cx.set_state(indoc! {"
25503        echo \"test\"25504    "});
25505    cx.update_editor(|editor, window, cx| {
25506        editor.newline(&Newline, window, cx);
25507    });
25508    cx.run_until_parked();
25509    cx.assert_editor_state(indoc! {"
25510        echo \"test\";
25511        ˇ
25512    "});
25513}
25514
25515fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
25516    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
25517    point..point
25518}
25519
25520#[track_caller]
25521fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
25522    let (text, ranges) = marked_text_ranges(marked_text, true);
25523    assert_eq!(editor.text(cx), text);
25524    assert_eq!(
25525        editor.selections.ranges(&editor.display_snapshot(cx)),
25526        ranges,
25527        "Assert selections are {}",
25528        marked_text
25529    );
25530}
25531
25532pub fn handle_signature_help_request(
25533    cx: &mut EditorLspTestContext,
25534    mocked_response: lsp::SignatureHelp,
25535) -> impl Future<Output = ()> + use<> {
25536    let mut request =
25537        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
25538            let mocked_response = mocked_response.clone();
25539            async move { Ok(Some(mocked_response)) }
25540        });
25541
25542    async move {
25543        request.next().await;
25544    }
25545}
25546
25547#[track_caller]
25548pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
25549    cx.update_editor(|editor, _, _| {
25550        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
25551            let entries = menu.entries.borrow();
25552            let entries = entries
25553                .iter()
25554                .map(|entry| entry.string.as_str())
25555                .collect::<Vec<_>>();
25556            assert_eq!(entries, expected);
25557        } else {
25558            panic!("Expected completions menu");
25559        }
25560    });
25561}
25562
25563/// Handle completion request passing a marked string specifying where the completion
25564/// should be triggered from using '|' character, what range should be replaced, and what completions
25565/// should be returned using '<' and '>' to delimit the range.
25566///
25567/// Also see `handle_completion_request_with_insert_and_replace`.
25568#[track_caller]
25569pub fn handle_completion_request(
25570    marked_string: &str,
25571    completions: Vec<&'static str>,
25572    is_incomplete: bool,
25573    counter: Arc<AtomicUsize>,
25574    cx: &mut EditorLspTestContext,
25575) -> impl Future<Output = ()> {
25576    let complete_from_marker: TextRangeMarker = '|'.into();
25577    let replace_range_marker: TextRangeMarker = ('<', '>').into();
25578    let (_, mut marked_ranges) = marked_text_ranges_by(
25579        marked_string,
25580        vec![complete_from_marker.clone(), replace_range_marker.clone()],
25581    );
25582
25583    let complete_from_position =
25584        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
25585    let replace_range =
25586        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
25587
25588    let mut request =
25589        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
25590            let completions = completions.clone();
25591            counter.fetch_add(1, atomic::Ordering::Release);
25592            async move {
25593                assert_eq!(params.text_document_position.text_document.uri, url.clone());
25594                assert_eq!(
25595                    params.text_document_position.position,
25596                    complete_from_position
25597                );
25598                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
25599                    is_incomplete,
25600                    item_defaults: None,
25601                    items: completions
25602                        .iter()
25603                        .map(|completion_text| lsp::CompletionItem {
25604                            label: completion_text.to_string(),
25605                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
25606                                range: replace_range,
25607                                new_text: completion_text.to_string(),
25608                            })),
25609                            ..Default::default()
25610                        })
25611                        .collect(),
25612                })))
25613            }
25614        });
25615
25616    async move {
25617        request.next().await;
25618    }
25619}
25620
25621/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
25622/// given instead, which also contains an `insert` range.
25623///
25624/// This function uses markers to define ranges:
25625/// - `|` marks the cursor position
25626/// - `<>` marks the replace range
25627/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
25628pub fn handle_completion_request_with_insert_and_replace(
25629    cx: &mut EditorLspTestContext,
25630    marked_string: &str,
25631    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
25632    counter: Arc<AtomicUsize>,
25633) -> impl Future<Output = ()> {
25634    let complete_from_marker: TextRangeMarker = '|'.into();
25635    let replace_range_marker: TextRangeMarker = ('<', '>').into();
25636    let insert_range_marker: TextRangeMarker = ('{', '}').into();
25637
25638    let (_, mut marked_ranges) = marked_text_ranges_by(
25639        marked_string,
25640        vec![
25641            complete_from_marker.clone(),
25642            replace_range_marker.clone(),
25643            insert_range_marker.clone(),
25644        ],
25645    );
25646
25647    let complete_from_position =
25648        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
25649    let replace_range =
25650        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
25651
25652    let insert_range = match marked_ranges.remove(&insert_range_marker) {
25653        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
25654        _ => lsp::Range {
25655            start: replace_range.start,
25656            end: complete_from_position,
25657        },
25658    };
25659
25660    let mut request =
25661        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
25662            let completions = completions.clone();
25663            counter.fetch_add(1, atomic::Ordering::Release);
25664            async move {
25665                assert_eq!(params.text_document_position.text_document.uri, url.clone());
25666                assert_eq!(
25667                    params.text_document_position.position, complete_from_position,
25668                    "marker `|` position doesn't match",
25669                );
25670                Ok(Some(lsp::CompletionResponse::Array(
25671                    completions
25672                        .iter()
25673                        .map(|(label, new_text)| lsp::CompletionItem {
25674                            label: label.to_string(),
25675                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
25676                                lsp::InsertReplaceEdit {
25677                                    insert: insert_range,
25678                                    replace: replace_range,
25679                                    new_text: new_text.to_string(),
25680                                },
25681                            )),
25682                            ..Default::default()
25683                        })
25684                        .collect(),
25685                )))
25686            }
25687        });
25688
25689    async move {
25690        request.next().await;
25691    }
25692}
25693
25694fn handle_resolve_completion_request(
25695    cx: &mut EditorLspTestContext,
25696    edits: Option<Vec<(&'static str, &'static str)>>,
25697) -> impl Future<Output = ()> {
25698    let edits = edits.map(|edits| {
25699        edits
25700            .iter()
25701            .map(|(marked_string, new_text)| {
25702                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
25703                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
25704                lsp::TextEdit::new(replace_range, new_text.to_string())
25705            })
25706            .collect::<Vec<_>>()
25707    });
25708
25709    let mut request =
25710        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
25711            let edits = edits.clone();
25712            async move {
25713                Ok(lsp::CompletionItem {
25714                    additional_text_edits: edits,
25715                    ..Default::default()
25716                })
25717            }
25718        });
25719
25720    async move {
25721        request.next().await;
25722    }
25723}
25724
25725pub(crate) fn update_test_language_settings(
25726    cx: &mut TestAppContext,
25727    f: impl Fn(&mut AllLanguageSettingsContent),
25728) {
25729    cx.update(|cx| {
25730        SettingsStore::update_global(cx, |store, cx| {
25731            store.update_user_settings(cx, |settings| f(&mut settings.project.all_languages));
25732        });
25733    });
25734}
25735
25736pub(crate) fn update_test_project_settings(
25737    cx: &mut TestAppContext,
25738    f: impl Fn(&mut ProjectSettingsContent),
25739) {
25740    cx.update(|cx| {
25741        SettingsStore::update_global(cx, |store, cx| {
25742            store.update_user_settings(cx, |settings| f(&mut settings.project));
25743        });
25744    });
25745}
25746
25747pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
25748    cx.update(|cx| {
25749        assets::Assets.load_test_fonts(cx);
25750        let store = SettingsStore::test(cx);
25751        cx.set_global(store);
25752        theme::init(theme::LoadThemes::JustBase, cx);
25753        release_channel::init(SemanticVersion::default(), cx);
25754        client::init_settings(cx);
25755        language::init(cx);
25756        Project::init_settings(cx);
25757        workspace::init_settings(cx);
25758        crate::init(cx);
25759    });
25760    zlog::init_test();
25761    update_test_language_settings(cx, f);
25762}
25763
25764#[track_caller]
25765fn assert_hunk_revert(
25766    not_reverted_text_with_selections: &str,
25767    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
25768    expected_reverted_text_with_selections: &str,
25769    base_text: &str,
25770    cx: &mut EditorLspTestContext,
25771) {
25772    cx.set_state(not_reverted_text_with_selections);
25773    cx.set_head_text(base_text);
25774    cx.executor().run_until_parked();
25775
25776    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
25777        let snapshot = editor.snapshot(window, cx);
25778        let reverted_hunk_statuses = snapshot
25779            .buffer_snapshot()
25780            .diff_hunks_in_range(0..snapshot.buffer_snapshot().len())
25781            .map(|hunk| hunk.status().kind)
25782            .collect::<Vec<_>>();
25783
25784        editor.git_restore(&Default::default(), window, cx);
25785        reverted_hunk_statuses
25786    });
25787    cx.executor().run_until_parked();
25788    cx.assert_editor_state(expected_reverted_text_with_selections);
25789    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
25790}
25791
25792#[gpui::test(iterations = 10)]
25793async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
25794    init_test(cx, |_| {});
25795
25796    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
25797    let counter = diagnostic_requests.clone();
25798
25799    let fs = FakeFs::new(cx.executor());
25800    fs.insert_tree(
25801        path!("/a"),
25802        json!({
25803            "first.rs": "fn main() { let a = 5; }",
25804            "second.rs": "// Test file",
25805        }),
25806    )
25807    .await;
25808
25809    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25810    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25811    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25812
25813    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25814    language_registry.add(rust_lang());
25815    let mut fake_servers = language_registry.register_fake_lsp(
25816        "Rust",
25817        FakeLspAdapter {
25818            capabilities: lsp::ServerCapabilities {
25819                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
25820                    lsp::DiagnosticOptions {
25821                        identifier: None,
25822                        inter_file_dependencies: true,
25823                        workspace_diagnostics: true,
25824                        work_done_progress_options: Default::default(),
25825                    },
25826                )),
25827                ..Default::default()
25828            },
25829            ..Default::default()
25830        },
25831    );
25832
25833    let editor = workspace
25834        .update(cx, |workspace, window, cx| {
25835            workspace.open_abs_path(
25836                PathBuf::from(path!("/a/first.rs")),
25837                OpenOptions::default(),
25838                window,
25839                cx,
25840            )
25841        })
25842        .unwrap()
25843        .await
25844        .unwrap()
25845        .downcast::<Editor>()
25846        .unwrap();
25847    let fake_server = fake_servers.next().await.unwrap();
25848    let server_id = fake_server.server.server_id();
25849    let mut first_request = fake_server
25850        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
25851            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
25852            let result_id = Some(new_result_id.to_string());
25853            assert_eq!(
25854                params.text_document.uri,
25855                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25856            );
25857            async move {
25858                Ok(lsp::DocumentDiagnosticReportResult::Report(
25859                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
25860                        related_documents: None,
25861                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
25862                            items: Vec::new(),
25863                            result_id,
25864                        },
25865                    }),
25866                ))
25867            }
25868        });
25869
25870    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
25871        project.update(cx, |project, cx| {
25872            let buffer_id = editor
25873                .read(cx)
25874                .buffer()
25875                .read(cx)
25876                .as_singleton()
25877                .expect("created a singleton buffer")
25878                .read(cx)
25879                .remote_id();
25880            let buffer_result_id = project
25881                .lsp_store()
25882                .read(cx)
25883                .result_id(server_id, buffer_id, cx);
25884            assert_eq!(expected, buffer_result_id);
25885        });
25886    };
25887
25888    ensure_result_id(None, cx);
25889    cx.executor().advance_clock(Duration::from_millis(60));
25890    cx.executor().run_until_parked();
25891    assert_eq!(
25892        diagnostic_requests.load(atomic::Ordering::Acquire),
25893        1,
25894        "Opening file should trigger diagnostic request"
25895    );
25896    first_request
25897        .next()
25898        .await
25899        .expect("should have sent the first diagnostics pull request");
25900    ensure_result_id(Some("1".to_string()), cx);
25901
25902    // Editing should trigger diagnostics
25903    editor.update_in(cx, |editor, window, cx| {
25904        editor.handle_input("2", window, cx)
25905    });
25906    cx.executor().advance_clock(Duration::from_millis(60));
25907    cx.executor().run_until_parked();
25908    assert_eq!(
25909        diagnostic_requests.load(atomic::Ordering::Acquire),
25910        2,
25911        "Editing should trigger diagnostic request"
25912    );
25913    ensure_result_id(Some("2".to_string()), cx);
25914
25915    // Moving cursor should not trigger diagnostic request
25916    editor.update_in(cx, |editor, window, cx| {
25917        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25918            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
25919        });
25920    });
25921    cx.executor().advance_clock(Duration::from_millis(60));
25922    cx.executor().run_until_parked();
25923    assert_eq!(
25924        diagnostic_requests.load(atomic::Ordering::Acquire),
25925        2,
25926        "Cursor movement should not trigger diagnostic request"
25927    );
25928    ensure_result_id(Some("2".to_string()), cx);
25929    // Multiple rapid edits should be debounced
25930    for _ in 0..5 {
25931        editor.update_in(cx, |editor, window, cx| {
25932            editor.handle_input("x", window, cx)
25933        });
25934    }
25935    cx.executor().advance_clock(Duration::from_millis(60));
25936    cx.executor().run_until_parked();
25937
25938    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
25939    assert!(
25940        final_requests <= 4,
25941        "Multiple rapid edits should be debounced (got {final_requests} requests)",
25942    );
25943    ensure_result_id(Some(final_requests.to_string()), cx);
25944}
25945
25946#[gpui::test]
25947async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
25948    // Regression test for issue #11671
25949    // Previously, adding a cursor after moving multiple cursors would reset
25950    // the cursor count instead of adding to the existing cursors.
25951    init_test(cx, |_| {});
25952    let mut cx = EditorTestContext::new(cx).await;
25953
25954    // Create a simple buffer with cursor at start
25955    cx.set_state(indoc! {"
25956        ˇaaaa
25957        bbbb
25958        cccc
25959        dddd
25960        eeee
25961        ffff
25962        gggg
25963        hhhh"});
25964
25965    // Add 2 cursors below (so we have 3 total)
25966    cx.update_editor(|editor, window, cx| {
25967        editor.add_selection_below(&Default::default(), window, cx);
25968        editor.add_selection_below(&Default::default(), window, cx);
25969    });
25970
25971    // Verify we have 3 cursors
25972    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
25973    assert_eq!(
25974        initial_count, 3,
25975        "Should have 3 cursors after adding 2 below"
25976    );
25977
25978    // Move down one line
25979    cx.update_editor(|editor, window, cx| {
25980        editor.move_down(&MoveDown, window, cx);
25981    });
25982
25983    // Add another cursor below
25984    cx.update_editor(|editor, window, cx| {
25985        editor.add_selection_below(&Default::default(), window, cx);
25986    });
25987
25988    // Should now have 4 cursors (3 original + 1 new)
25989    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
25990    assert_eq!(
25991        final_count, 4,
25992        "Should have 4 cursors after moving and adding another"
25993    );
25994}
25995
25996#[gpui::test]
25997async fn test_add_selection_skip_soft_wrap_option(cx: &mut TestAppContext) {
25998    init_test(cx, |_| {});
25999
26000    let mut cx = EditorTestContext::new(cx).await;
26001
26002    cx.set_state(indoc!(
26003        r#"ˇThis is a very long line that will be wrapped when soft wrapping is enabled
26004           Second line here"#
26005    ));
26006
26007    cx.update_editor(|editor, window, cx| {
26008        // Enable soft wrapping with a narrow width to force soft wrapping and
26009        // confirm that more than 2 rows are being displayed.
26010        editor.set_wrap_width(Some(100.0.into()), cx);
26011        assert!(editor.display_text(cx).lines().count() > 2);
26012
26013        editor.add_selection_below(
26014            &AddSelectionBelow {
26015                skip_soft_wrap: true,
26016            },
26017            window,
26018            cx,
26019        );
26020
26021        assert_eq!(
26022            editor.selections.display_ranges(cx),
26023            &[
26024                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
26025                DisplayPoint::new(DisplayRow(8), 0)..DisplayPoint::new(DisplayRow(8), 0),
26026            ]
26027        );
26028
26029        editor.add_selection_above(
26030            &AddSelectionAbove {
26031                skip_soft_wrap: true,
26032            },
26033            window,
26034            cx,
26035        );
26036
26037        assert_eq!(
26038            editor.selections.display_ranges(cx),
26039            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
26040        );
26041
26042        editor.add_selection_below(
26043            &AddSelectionBelow {
26044                skip_soft_wrap: false,
26045            },
26046            window,
26047            cx,
26048        );
26049
26050        assert_eq!(
26051            editor.selections.display_ranges(cx),
26052            &[
26053                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
26054                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
26055            ]
26056        );
26057
26058        editor.add_selection_above(
26059            &AddSelectionAbove {
26060                skip_soft_wrap: false,
26061            },
26062            window,
26063            cx,
26064        );
26065
26066        assert_eq!(
26067            editor.selections.display_ranges(cx),
26068            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
26069        );
26070    });
26071}
26072
26073#[gpui::test(iterations = 10)]
26074async fn test_document_colors(cx: &mut TestAppContext) {
26075    let expected_color = Rgba {
26076        r: 0.33,
26077        g: 0.33,
26078        b: 0.33,
26079        a: 0.33,
26080    };
26081
26082    init_test(cx, |_| {});
26083
26084    let fs = FakeFs::new(cx.executor());
26085    fs.insert_tree(
26086        path!("/a"),
26087        json!({
26088            "first.rs": "fn main() { let a = 5; }",
26089        }),
26090    )
26091    .await;
26092
26093    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
26094    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
26095    let cx = &mut VisualTestContext::from_window(*workspace, cx);
26096
26097    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
26098    language_registry.add(rust_lang());
26099    let mut fake_servers = language_registry.register_fake_lsp(
26100        "Rust",
26101        FakeLspAdapter {
26102            capabilities: lsp::ServerCapabilities {
26103                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
26104                ..lsp::ServerCapabilities::default()
26105            },
26106            name: "rust-analyzer",
26107            ..FakeLspAdapter::default()
26108        },
26109    );
26110    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
26111        "Rust",
26112        FakeLspAdapter {
26113            capabilities: lsp::ServerCapabilities {
26114                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
26115                ..lsp::ServerCapabilities::default()
26116            },
26117            name: "not-rust-analyzer",
26118            ..FakeLspAdapter::default()
26119        },
26120    );
26121
26122    let editor = workspace
26123        .update(cx, |workspace, window, cx| {
26124            workspace.open_abs_path(
26125                PathBuf::from(path!("/a/first.rs")),
26126                OpenOptions::default(),
26127                window,
26128                cx,
26129            )
26130        })
26131        .unwrap()
26132        .await
26133        .unwrap()
26134        .downcast::<Editor>()
26135        .unwrap();
26136    let fake_language_server = fake_servers.next().await.unwrap();
26137    let fake_language_server_without_capabilities =
26138        fake_servers_without_capabilities.next().await.unwrap();
26139    let requests_made = Arc::new(AtomicUsize::new(0));
26140    let closure_requests_made = Arc::clone(&requests_made);
26141    let mut color_request_handle = fake_language_server
26142        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
26143            let requests_made = Arc::clone(&closure_requests_made);
26144            async move {
26145                assert_eq!(
26146                    params.text_document.uri,
26147                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
26148                );
26149                requests_made.fetch_add(1, atomic::Ordering::Release);
26150                Ok(vec![
26151                    lsp::ColorInformation {
26152                        range: lsp::Range {
26153                            start: lsp::Position {
26154                                line: 0,
26155                                character: 0,
26156                            },
26157                            end: lsp::Position {
26158                                line: 0,
26159                                character: 1,
26160                            },
26161                        },
26162                        color: lsp::Color {
26163                            red: 0.33,
26164                            green: 0.33,
26165                            blue: 0.33,
26166                            alpha: 0.33,
26167                        },
26168                    },
26169                    lsp::ColorInformation {
26170                        range: lsp::Range {
26171                            start: lsp::Position {
26172                                line: 0,
26173                                character: 0,
26174                            },
26175                            end: lsp::Position {
26176                                line: 0,
26177                                character: 1,
26178                            },
26179                        },
26180                        color: lsp::Color {
26181                            red: 0.33,
26182                            green: 0.33,
26183                            blue: 0.33,
26184                            alpha: 0.33,
26185                        },
26186                    },
26187                ])
26188            }
26189        });
26190
26191    let _handle = fake_language_server_without_capabilities
26192        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
26193            panic!("Should not be called");
26194        });
26195    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
26196    color_request_handle.next().await.unwrap();
26197    cx.run_until_parked();
26198    assert_eq!(
26199        1,
26200        requests_made.load(atomic::Ordering::Acquire),
26201        "Should query for colors once per editor open"
26202    );
26203    editor.update_in(cx, |editor, _, cx| {
26204        assert_eq!(
26205            vec![expected_color],
26206            extract_color_inlays(editor, cx),
26207            "Should have an initial inlay"
26208        );
26209    });
26210
26211    // opening another file in a split should not influence the LSP query counter
26212    workspace
26213        .update(cx, |workspace, window, cx| {
26214            assert_eq!(
26215                workspace.panes().len(),
26216                1,
26217                "Should have one pane with one editor"
26218            );
26219            workspace.move_item_to_pane_in_direction(
26220                &MoveItemToPaneInDirection {
26221                    direction: SplitDirection::Right,
26222                    focus: false,
26223                    clone: true,
26224                },
26225                window,
26226                cx,
26227            );
26228        })
26229        .unwrap();
26230    cx.run_until_parked();
26231    workspace
26232        .update(cx, |workspace, _, cx| {
26233            let panes = workspace.panes();
26234            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
26235            for pane in panes {
26236                let editor = pane
26237                    .read(cx)
26238                    .active_item()
26239                    .and_then(|item| item.downcast::<Editor>())
26240                    .expect("Should have opened an editor in each split");
26241                let editor_file = editor
26242                    .read(cx)
26243                    .buffer()
26244                    .read(cx)
26245                    .as_singleton()
26246                    .expect("test deals with singleton buffers")
26247                    .read(cx)
26248                    .file()
26249                    .expect("test buffese should have a file")
26250                    .path();
26251                assert_eq!(
26252                    editor_file.as_ref(),
26253                    rel_path("first.rs"),
26254                    "Both editors should be opened for the same file"
26255                )
26256            }
26257        })
26258        .unwrap();
26259
26260    cx.executor().advance_clock(Duration::from_millis(500));
26261    let save = editor.update_in(cx, |editor, window, cx| {
26262        editor.move_to_end(&MoveToEnd, window, cx);
26263        editor.handle_input("dirty", window, cx);
26264        editor.save(
26265            SaveOptions {
26266                format: true,
26267                autosave: true,
26268            },
26269            project.clone(),
26270            window,
26271            cx,
26272        )
26273    });
26274    save.await.unwrap();
26275
26276    color_request_handle.next().await.unwrap();
26277    cx.run_until_parked();
26278    assert_eq!(
26279        2,
26280        requests_made.load(atomic::Ordering::Acquire),
26281        "Should query for colors once per save (deduplicated) and once per formatting after save"
26282    );
26283
26284    drop(editor);
26285    let close = workspace
26286        .update(cx, |workspace, window, cx| {
26287            workspace.active_pane().update(cx, |pane, cx| {
26288                pane.close_active_item(&CloseActiveItem::default(), window, cx)
26289            })
26290        })
26291        .unwrap();
26292    close.await.unwrap();
26293    let close = workspace
26294        .update(cx, |workspace, window, cx| {
26295            workspace.active_pane().update(cx, |pane, cx| {
26296                pane.close_active_item(&CloseActiveItem::default(), window, cx)
26297            })
26298        })
26299        .unwrap();
26300    close.await.unwrap();
26301    assert_eq!(
26302        2,
26303        requests_made.load(atomic::Ordering::Acquire),
26304        "After saving and closing all editors, no extra requests should be made"
26305    );
26306    workspace
26307        .update(cx, |workspace, _, cx| {
26308            assert!(
26309                workspace.active_item(cx).is_none(),
26310                "Should close all editors"
26311            )
26312        })
26313        .unwrap();
26314
26315    workspace
26316        .update(cx, |workspace, window, cx| {
26317            workspace.active_pane().update(cx, |pane, cx| {
26318                pane.navigate_backward(&workspace::GoBack, window, cx);
26319            })
26320        })
26321        .unwrap();
26322    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
26323    cx.run_until_parked();
26324    let editor = workspace
26325        .update(cx, |workspace, _, cx| {
26326            workspace
26327                .active_item(cx)
26328                .expect("Should have reopened the editor again after navigating back")
26329                .downcast::<Editor>()
26330                .expect("Should be an editor")
26331        })
26332        .unwrap();
26333
26334    assert_eq!(
26335        2,
26336        requests_made.load(atomic::Ordering::Acquire),
26337        "Cache should be reused on buffer close and reopen"
26338    );
26339    editor.update(cx, |editor, cx| {
26340        assert_eq!(
26341            vec![expected_color],
26342            extract_color_inlays(editor, cx),
26343            "Should have an initial inlay"
26344        );
26345    });
26346
26347    drop(color_request_handle);
26348    let closure_requests_made = Arc::clone(&requests_made);
26349    let mut empty_color_request_handle = fake_language_server
26350        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
26351            let requests_made = Arc::clone(&closure_requests_made);
26352            async move {
26353                assert_eq!(
26354                    params.text_document.uri,
26355                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
26356                );
26357                requests_made.fetch_add(1, atomic::Ordering::Release);
26358                Ok(Vec::new())
26359            }
26360        });
26361    let save = editor.update_in(cx, |editor, window, cx| {
26362        editor.move_to_end(&MoveToEnd, window, cx);
26363        editor.handle_input("dirty_again", window, cx);
26364        editor.save(
26365            SaveOptions {
26366                format: false,
26367                autosave: true,
26368            },
26369            project.clone(),
26370            window,
26371            cx,
26372        )
26373    });
26374    save.await.unwrap();
26375
26376    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
26377    empty_color_request_handle.next().await.unwrap();
26378    cx.run_until_parked();
26379    assert_eq!(
26380        3,
26381        requests_made.load(atomic::Ordering::Acquire),
26382        "Should query for colors once per save only, as formatting was not requested"
26383    );
26384    editor.update(cx, |editor, cx| {
26385        assert_eq!(
26386            Vec::<Rgba>::new(),
26387            extract_color_inlays(editor, cx),
26388            "Should clear all colors when the server returns an empty response"
26389        );
26390    });
26391}
26392
26393#[gpui::test]
26394async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
26395    init_test(cx, |_| {});
26396    let (editor, cx) = cx.add_window_view(Editor::single_line);
26397    editor.update_in(cx, |editor, window, cx| {
26398        editor.set_text("oops\n\nwow\n", window, cx)
26399    });
26400    cx.run_until_parked();
26401    editor.update(cx, |editor, cx| {
26402        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
26403    });
26404    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
26405    cx.run_until_parked();
26406    editor.update(cx, |editor, cx| {
26407        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
26408    });
26409}
26410
26411#[gpui::test]
26412async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
26413    init_test(cx, |_| {});
26414
26415    cx.update(|cx| {
26416        register_project_item::<Editor>(cx);
26417    });
26418
26419    let fs = FakeFs::new(cx.executor());
26420    fs.insert_tree("/root1", json!({})).await;
26421    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
26422        .await;
26423
26424    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
26425    let (workspace, cx) =
26426        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
26427
26428    let worktree_id = project.update(cx, |project, cx| {
26429        project.worktrees(cx).next().unwrap().read(cx).id()
26430    });
26431
26432    let handle = workspace
26433        .update_in(cx, |workspace, window, cx| {
26434            let project_path = (worktree_id, rel_path("one.pdf"));
26435            workspace.open_path(project_path, None, true, window, cx)
26436        })
26437        .await
26438        .unwrap();
26439
26440    assert_eq!(
26441        handle.to_any().entity_type(),
26442        TypeId::of::<InvalidItemView>()
26443    );
26444}
26445
26446#[gpui::test]
26447async fn test_select_next_prev_syntax_node(cx: &mut TestAppContext) {
26448    init_test(cx, |_| {});
26449
26450    let language = Arc::new(Language::new(
26451        LanguageConfig::default(),
26452        Some(tree_sitter_rust::LANGUAGE.into()),
26453    ));
26454
26455    // Test hierarchical sibling navigation
26456    let text = r#"
26457        fn outer() {
26458            if condition {
26459                let a = 1;
26460            }
26461            let b = 2;
26462        }
26463
26464        fn another() {
26465            let c = 3;
26466        }
26467    "#;
26468
26469    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
26470    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
26471    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
26472
26473    // Wait for parsing to complete
26474    editor
26475        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
26476        .await;
26477
26478    editor.update_in(cx, |editor, window, cx| {
26479        // Start by selecting "let a = 1;" inside the if block
26480        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26481            s.select_display_ranges([
26482                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 26)
26483            ]);
26484        });
26485
26486        let initial_selection = editor.selections.display_ranges(cx);
26487        assert_eq!(initial_selection.len(), 1, "Should have one selection");
26488
26489        // Test select next sibling - should move up levels to find the next sibling
26490        // Since "let a = 1;" has no siblings in the if block, it should move up
26491        // to find "let b = 2;" which is a sibling of the if block
26492        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
26493        let next_selection = editor.selections.display_ranges(cx);
26494
26495        // Should have a selection and it should be different from the initial
26496        assert_eq!(
26497            next_selection.len(),
26498            1,
26499            "Should have one selection after next"
26500        );
26501        assert_ne!(
26502            next_selection[0], initial_selection[0],
26503            "Next sibling selection should be different"
26504        );
26505
26506        // Test hierarchical navigation by going to the end of the current function
26507        // and trying to navigate to the next function
26508        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26509            s.select_display_ranges([
26510                DisplayPoint::new(DisplayRow(5), 12)..DisplayPoint::new(DisplayRow(5), 22)
26511            ]);
26512        });
26513
26514        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
26515        let function_next_selection = editor.selections.display_ranges(cx);
26516
26517        // Should move to the next function
26518        assert_eq!(
26519            function_next_selection.len(),
26520            1,
26521            "Should have one selection after function next"
26522        );
26523
26524        // Test select previous sibling navigation
26525        editor.select_prev_syntax_node(&SelectPreviousSyntaxNode, window, cx);
26526        let prev_selection = editor.selections.display_ranges(cx);
26527
26528        // Should have a selection and it should be different
26529        assert_eq!(
26530            prev_selection.len(),
26531            1,
26532            "Should have one selection after prev"
26533        );
26534        assert_ne!(
26535            prev_selection[0], function_next_selection[0],
26536            "Previous sibling selection should be different from next"
26537        );
26538    });
26539}
26540
26541#[gpui::test]
26542async fn test_next_prev_document_highlight(cx: &mut TestAppContext) {
26543    init_test(cx, |_| {});
26544
26545    let mut cx = EditorTestContext::new(cx).await;
26546    cx.set_state(
26547        "let ˇvariable = 42;
26548let another = variable + 1;
26549let result = variable * 2;",
26550    );
26551
26552    // Set up document highlights manually (simulating LSP response)
26553    cx.update_editor(|editor, _window, cx| {
26554        let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
26555
26556        // Create highlights for "variable" occurrences
26557        let highlight_ranges = [
26558            Point::new(0, 4)..Point::new(0, 12),  // First "variable"
26559            Point::new(1, 14)..Point::new(1, 22), // Second "variable"
26560            Point::new(2, 13)..Point::new(2, 21), // Third "variable"
26561        ];
26562
26563        let anchor_ranges: Vec<_> = highlight_ranges
26564            .iter()
26565            .map(|range| range.clone().to_anchors(&buffer_snapshot))
26566            .collect();
26567
26568        editor.highlight_background::<DocumentHighlightRead>(
26569            &anchor_ranges,
26570            |theme| theme.colors().editor_document_highlight_read_background,
26571            cx,
26572        );
26573    });
26574
26575    // Go to next highlight - should move to second "variable"
26576    cx.update_editor(|editor, window, cx| {
26577        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26578    });
26579    cx.assert_editor_state(
26580        "let variable = 42;
26581let another = ˇvariable + 1;
26582let result = variable * 2;",
26583    );
26584
26585    // Go to next highlight - should move to third "variable"
26586    cx.update_editor(|editor, window, cx| {
26587        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26588    });
26589    cx.assert_editor_state(
26590        "let variable = 42;
26591let another = variable + 1;
26592let result = ˇvariable * 2;",
26593    );
26594
26595    // Go to next highlight - should stay at third "variable" (no wrap-around)
26596    cx.update_editor(|editor, window, cx| {
26597        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26598    });
26599    cx.assert_editor_state(
26600        "let variable = 42;
26601let another = variable + 1;
26602let result = ˇvariable * 2;",
26603    );
26604
26605    // Now test going backwards from third position
26606    cx.update_editor(|editor, window, cx| {
26607        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26608    });
26609    cx.assert_editor_state(
26610        "let variable = 42;
26611let another = ˇvariable + 1;
26612let result = variable * 2;",
26613    );
26614
26615    // Go to previous highlight - should move to first "variable"
26616    cx.update_editor(|editor, window, cx| {
26617        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26618    });
26619    cx.assert_editor_state(
26620        "let ˇvariable = 42;
26621let another = variable + 1;
26622let result = variable * 2;",
26623    );
26624
26625    // Go to previous highlight - should stay on first "variable"
26626    cx.update_editor(|editor, window, cx| {
26627        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26628    });
26629    cx.assert_editor_state(
26630        "let ˇvariable = 42;
26631let another = variable + 1;
26632let result = variable * 2;",
26633    );
26634}
26635
26636#[gpui::test]
26637async fn test_paste_url_from_other_app_creates_markdown_link_over_selected_text(
26638    cx: &mut gpui::TestAppContext,
26639) {
26640    init_test(cx, |_| {});
26641
26642    let url = "https://zed.dev";
26643
26644    let markdown_language = Arc::new(Language::new(
26645        LanguageConfig {
26646            name: "Markdown".into(),
26647            ..LanguageConfig::default()
26648        },
26649        None,
26650    ));
26651
26652    let mut cx = EditorTestContext::new(cx).await;
26653    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26654    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)");
26655
26656    cx.update_editor(|editor, window, cx| {
26657        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26658        editor.paste(&Paste, window, cx);
26659    });
26660
26661    cx.assert_editor_state(&format!(
26662        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)"
26663    ));
26664}
26665
26666#[gpui::test]
26667async fn test_paste_url_from_zed_copy_creates_markdown_link_over_selected_text(
26668    cx: &mut gpui::TestAppContext,
26669) {
26670    init_test(cx, |_| {});
26671
26672    let url = "https://zed.dev";
26673
26674    let markdown_language = Arc::new(Language::new(
26675        LanguageConfig {
26676            name: "Markdown".into(),
26677            ..LanguageConfig::default()
26678        },
26679        None,
26680    ));
26681
26682    let mut cx = EditorTestContext::new(cx).await;
26683    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26684    cx.set_state(&format!(
26685        "Hello, editor.\nZed is great (see this link: )\n«{url}ˇ»"
26686    ));
26687
26688    cx.update_editor(|editor, window, cx| {
26689        editor.copy(&Copy, window, cx);
26690    });
26691
26692    cx.set_state(&format!(
26693        "Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)\n{url}"
26694    ));
26695
26696    cx.update_editor(|editor, window, cx| {
26697        editor.paste(&Paste, window, cx);
26698    });
26699
26700    cx.assert_editor_state(&format!(
26701        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)\n{url}"
26702    ));
26703}
26704
26705#[gpui::test]
26706async fn test_paste_url_from_other_app_replaces_existing_url_without_creating_markdown_link(
26707    cx: &mut gpui::TestAppContext,
26708) {
26709    init_test(cx, |_| {});
26710
26711    let url = "https://zed.dev";
26712
26713    let markdown_language = Arc::new(Language::new(
26714        LanguageConfig {
26715            name: "Markdown".into(),
26716            ..LanguageConfig::default()
26717        },
26718        None,
26719    ));
26720
26721    let mut cx = EditorTestContext::new(cx).await;
26722    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26723    cx.set_state("Please visit zed's homepage: «https://www.apple.comˇ»");
26724
26725    cx.update_editor(|editor, window, cx| {
26726        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26727        editor.paste(&Paste, window, cx);
26728    });
26729
26730    cx.assert_editor_state(&format!("Please visit zed's homepage: {url}ˇ"));
26731}
26732
26733#[gpui::test]
26734async fn test_paste_plain_text_from_other_app_replaces_selection_without_creating_markdown_link(
26735    cx: &mut gpui::TestAppContext,
26736) {
26737    init_test(cx, |_| {});
26738
26739    let text = "Awesome";
26740
26741    let markdown_language = Arc::new(Language::new(
26742        LanguageConfig {
26743            name: "Markdown".into(),
26744            ..LanguageConfig::default()
26745        },
26746        None,
26747    ));
26748
26749    let mut cx = EditorTestContext::new(cx).await;
26750    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26751    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat»");
26752
26753    cx.update_editor(|editor, window, cx| {
26754        cx.write_to_clipboard(ClipboardItem::new_string(text.to_string()));
26755        editor.paste(&Paste, window, cx);
26756    });
26757
26758    cx.assert_editor_state(&format!("Hello, {text}ˇ.\nZed is {text}ˇ"));
26759}
26760
26761#[gpui::test]
26762async fn test_paste_url_from_other_app_without_creating_markdown_link_in_non_markdown_language(
26763    cx: &mut gpui::TestAppContext,
26764) {
26765    init_test(cx, |_| {});
26766
26767    let url = "https://zed.dev";
26768
26769    let markdown_language = Arc::new(Language::new(
26770        LanguageConfig {
26771            name: "Rust".into(),
26772            ..LanguageConfig::default()
26773        },
26774        None,
26775    ));
26776
26777    let mut cx = EditorTestContext::new(cx).await;
26778    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26779    cx.set_state("// Hello, «editorˇ».\n// Zed is «ˇgreat» (see this link: ˇ)");
26780
26781    cx.update_editor(|editor, window, cx| {
26782        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26783        editor.paste(&Paste, window, cx);
26784    });
26785
26786    cx.assert_editor_state(&format!(
26787        "// Hello, {url}ˇ.\n// Zed is {url}ˇ (see this link: {url}ˇ)"
26788    ));
26789}
26790
26791#[gpui::test]
26792async fn test_paste_url_from_other_app_creates_markdown_link_selectively_in_multi_buffer(
26793    cx: &mut TestAppContext,
26794) {
26795    init_test(cx, |_| {});
26796
26797    let url = "https://zed.dev";
26798
26799    let markdown_language = Arc::new(Language::new(
26800        LanguageConfig {
26801            name: "Markdown".into(),
26802            ..LanguageConfig::default()
26803        },
26804        None,
26805    ));
26806
26807    let (editor, cx) = cx.add_window_view(|window, cx| {
26808        let multi_buffer = MultiBuffer::build_multi(
26809            [
26810                ("this will embed -> link", vec![Point::row_range(0..1)]),
26811                ("this will replace -> link", vec![Point::row_range(0..1)]),
26812            ],
26813            cx,
26814        );
26815        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
26816        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26817            s.select_ranges(vec![
26818                Point::new(0, 19)..Point::new(0, 23),
26819                Point::new(1, 21)..Point::new(1, 25),
26820            ])
26821        });
26822        let first_buffer_id = multi_buffer
26823            .read(cx)
26824            .excerpt_buffer_ids()
26825            .into_iter()
26826            .next()
26827            .unwrap();
26828        let first_buffer = multi_buffer.read(cx).buffer(first_buffer_id).unwrap();
26829        first_buffer.update(cx, |buffer, cx| {
26830            buffer.set_language(Some(markdown_language.clone()), cx);
26831        });
26832
26833        editor
26834    });
26835    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
26836
26837    cx.update_editor(|editor, window, cx| {
26838        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26839        editor.paste(&Paste, window, cx);
26840    });
26841
26842    cx.assert_editor_state(&format!(
26843        "this will embed -> [link]({url}\nthis will replace -> {url}ˇ"
26844    ));
26845}
26846
26847#[gpui::test]
26848async fn test_race_in_multibuffer_save(cx: &mut TestAppContext) {
26849    init_test(cx, |_| {});
26850
26851    let fs = FakeFs::new(cx.executor());
26852    fs.insert_tree(
26853        path!("/project"),
26854        json!({
26855            "first.rs": "# First Document\nSome content here.",
26856            "second.rs": "Plain text content for second file.",
26857        }),
26858    )
26859    .await;
26860
26861    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
26862    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
26863    let cx = &mut VisualTestContext::from_window(*workspace, cx);
26864
26865    let language = rust_lang();
26866    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
26867    language_registry.add(language.clone());
26868    let mut fake_servers = language_registry.register_fake_lsp(
26869        "Rust",
26870        FakeLspAdapter {
26871            ..FakeLspAdapter::default()
26872        },
26873    );
26874
26875    let buffer1 = project
26876        .update(cx, |project, cx| {
26877            project.open_local_buffer(PathBuf::from(path!("/project/first.rs")), cx)
26878        })
26879        .await
26880        .unwrap();
26881    let buffer2 = project
26882        .update(cx, |project, cx| {
26883            project.open_local_buffer(PathBuf::from(path!("/project/second.rs")), cx)
26884        })
26885        .await
26886        .unwrap();
26887
26888    let multi_buffer = cx.new(|cx| {
26889        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
26890        multi_buffer.set_excerpts_for_path(
26891            PathKey::for_buffer(&buffer1, cx),
26892            buffer1.clone(),
26893            [Point::zero()..buffer1.read(cx).max_point()],
26894            3,
26895            cx,
26896        );
26897        multi_buffer.set_excerpts_for_path(
26898            PathKey::for_buffer(&buffer2, cx),
26899            buffer2.clone(),
26900            [Point::zero()..buffer1.read(cx).max_point()],
26901            3,
26902            cx,
26903        );
26904        multi_buffer
26905    });
26906
26907    let (editor, cx) = cx.add_window_view(|window, cx| {
26908        Editor::new(
26909            EditorMode::full(),
26910            multi_buffer,
26911            Some(project.clone()),
26912            window,
26913            cx,
26914        )
26915    });
26916
26917    let fake_language_server = fake_servers.next().await.unwrap();
26918
26919    buffer1.update(cx, |buffer, cx| buffer.edit([(0..0, "hello!")], None, cx));
26920
26921    let save = editor.update_in(cx, |editor, window, cx| {
26922        assert!(editor.is_dirty(cx));
26923
26924        editor.save(
26925            SaveOptions {
26926                format: true,
26927                autosave: true,
26928            },
26929            project,
26930            window,
26931            cx,
26932        )
26933    });
26934    let (start_edit_tx, start_edit_rx) = oneshot::channel();
26935    let (done_edit_tx, done_edit_rx) = oneshot::channel();
26936    let mut done_edit_rx = Some(done_edit_rx);
26937    let mut start_edit_tx = Some(start_edit_tx);
26938
26939    fake_language_server.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| {
26940        start_edit_tx.take().unwrap().send(()).unwrap();
26941        let done_edit_rx = done_edit_rx.take().unwrap();
26942        async move {
26943            done_edit_rx.await.unwrap();
26944            Ok(None)
26945        }
26946    });
26947
26948    start_edit_rx.await.unwrap();
26949    buffer2
26950        .update(cx, |buffer, cx| buffer.edit([(0..0, "world!")], None, cx))
26951        .unwrap();
26952
26953    done_edit_tx.send(()).unwrap();
26954
26955    save.await.unwrap();
26956    cx.update(|_, cx| assert!(editor.is_dirty(cx)));
26957}
26958
26959#[track_caller]
26960fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
26961    editor
26962        .all_inlays(cx)
26963        .into_iter()
26964        .filter_map(|inlay| inlay.get_color())
26965        .map(Rgba::from)
26966        .collect()
26967}
26968
26969#[gpui::test]
26970fn test_duplicate_line_up_on_last_line_without_newline(cx: &mut TestAppContext) {
26971    init_test(cx, |_| {});
26972
26973    let editor = cx.add_window(|window, cx| {
26974        let buffer = MultiBuffer::build_simple("line1\nline2", cx);
26975        build_editor(buffer, window, cx)
26976    });
26977
26978    editor
26979        .update(cx, |editor, window, cx| {
26980            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26981                s.select_display_ranges([
26982                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
26983                ])
26984            });
26985
26986            editor.duplicate_line_up(&DuplicateLineUp, window, cx);
26987
26988            assert_eq!(
26989                editor.display_text(cx),
26990                "line1\nline2\nline2",
26991                "Duplicating last line upward should create duplicate above, not on same line"
26992            );
26993
26994            assert_eq!(
26995                editor.selections.display_ranges(cx),
26996                vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)],
26997                "Selection should move to the duplicated line"
26998            );
26999        })
27000        .unwrap();
27001}
27002
27003#[gpui::test]
27004async fn test_copy_line_without_trailing_newline(cx: &mut TestAppContext) {
27005    init_test(cx, |_| {});
27006
27007    let mut cx = EditorTestContext::new(cx).await;
27008
27009    cx.set_state("line1\nline2ˇ");
27010
27011    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
27012
27013    let clipboard_text = cx
27014        .read_from_clipboard()
27015        .and_then(|item| item.text().as_deref().map(str::to_string));
27016
27017    assert_eq!(
27018        clipboard_text,
27019        Some("line2\n".to_string()),
27020        "Copying a line without trailing newline should include a newline"
27021    );
27022
27023    cx.set_state("line1\nˇ");
27024
27025    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
27026
27027    cx.assert_editor_state("line1\nline2\nˇ");
27028}
27029
27030#[gpui::test]
27031async fn test_end_of_editor_context(cx: &mut TestAppContext) {
27032    init_test(cx, |_| {});
27033
27034    let mut cx = EditorTestContext::new(cx).await;
27035
27036    cx.set_state("line1\nline2ˇ");
27037    cx.update_editor(|e, window, cx| {
27038        e.set_mode(EditorMode::SingleLine);
27039        assert!(e.key_context(window, cx).contains("end_of_input"));
27040    });
27041    cx.set_state("ˇline1\nline2");
27042    cx.update_editor(|e, window, cx| {
27043        assert!(!e.key_context(window, cx).contains("end_of_input"));
27044    });
27045    cx.set_state("line1ˇ\nline2");
27046    cx.update_editor(|e, window, cx| {
27047        assert!(!e.key_context(window, cx).contains("end_of_input"));
27048    });
27049}
27050
27051#[gpui::test]
27052async fn test_next_prev_reference(cx: &mut TestAppContext) {
27053    const CYCLE_POSITIONS: &[&'static str] = &[
27054        indoc! {"
27055            fn foo() {
27056                let ˇabc = 123;
27057                let x = abc + 1;
27058                let y = abc + 2;
27059                let z = abc + 2;
27060            }
27061        "},
27062        indoc! {"
27063            fn foo() {
27064                let abc = 123;
27065                let x = ˇabc + 1;
27066                let y = abc + 2;
27067                let z = abc + 2;
27068            }
27069        "},
27070        indoc! {"
27071            fn foo() {
27072                let abc = 123;
27073                let x = abc + 1;
27074                let y = ˇabc + 2;
27075                let z = abc + 2;
27076            }
27077        "},
27078        indoc! {"
27079            fn foo() {
27080                let abc = 123;
27081                let x = abc + 1;
27082                let y = abc + 2;
27083                let z = ˇabc + 2;
27084            }
27085        "},
27086    ];
27087
27088    init_test(cx, |_| {});
27089
27090    let mut cx = EditorLspTestContext::new_rust(
27091        lsp::ServerCapabilities {
27092            references_provider: Some(lsp::OneOf::Left(true)),
27093            ..Default::default()
27094        },
27095        cx,
27096    )
27097    .await;
27098
27099    // importantly, the cursor is in the middle
27100    cx.set_state(indoc! {"
27101        fn foo() {
27102            let aˇbc = 123;
27103            let x = abc + 1;
27104            let y = abc + 2;
27105            let z = abc + 2;
27106        }
27107    "});
27108
27109    let reference_ranges = [
27110        lsp::Position::new(1, 8),
27111        lsp::Position::new(2, 12),
27112        lsp::Position::new(3, 12),
27113        lsp::Position::new(4, 12),
27114    ]
27115    .map(|start| lsp::Range::new(start, lsp::Position::new(start.line, start.character + 3)));
27116
27117    cx.lsp
27118        .set_request_handler::<lsp::request::References, _, _>(move |params, _cx| async move {
27119            Ok(Some(
27120                reference_ranges
27121                    .map(|range| lsp::Location {
27122                        uri: params.text_document_position.text_document.uri.clone(),
27123                        range,
27124                    })
27125                    .to_vec(),
27126            ))
27127        });
27128
27129    let _move = async |direction, count, cx: &mut EditorLspTestContext| {
27130        cx.update_editor(|editor, window, cx| {
27131            editor.go_to_reference_before_or_after_position(direction, count, window, cx)
27132        })
27133        .unwrap()
27134        .await
27135        .unwrap()
27136    };
27137
27138    _move(Direction::Next, 1, &mut cx).await;
27139    cx.assert_editor_state(CYCLE_POSITIONS[1]);
27140
27141    _move(Direction::Next, 1, &mut cx).await;
27142    cx.assert_editor_state(CYCLE_POSITIONS[2]);
27143
27144    _move(Direction::Next, 1, &mut cx).await;
27145    cx.assert_editor_state(CYCLE_POSITIONS[3]);
27146
27147    // loops back to the start
27148    _move(Direction::Next, 1, &mut cx).await;
27149    cx.assert_editor_state(CYCLE_POSITIONS[0]);
27150
27151    // loops back to the end
27152    _move(Direction::Prev, 1, &mut cx).await;
27153    cx.assert_editor_state(CYCLE_POSITIONS[3]);
27154
27155    _move(Direction::Prev, 1, &mut cx).await;
27156    cx.assert_editor_state(CYCLE_POSITIONS[2]);
27157
27158    _move(Direction::Prev, 1, &mut cx).await;
27159    cx.assert_editor_state(CYCLE_POSITIONS[1]);
27160
27161    _move(Direction::Prev, 1, &mut cx).await;
27162    cx.assert_editor_state(CYCLE_POSITIONS[0]);
27163
27164    _move(Direction::Next, 3, &mut cx).await;
27165    cx.assert_editor_state(CYCLE_POSITIONS[3]);
27166
27167    _move(Direction::Prev, 2, &mut cx).await;
27168    cx.assert_editor_state(CYCLE_POSITIONS[1]);
27169}