editor_tests.rs

    1use super::*;
    2use crate::{
    3    JoinLines,
    4    code_context_menus::CodeContextMenu,
    5    edit_prediction_tests::FakeEditPredictionProvider,
    6    linked_editing_ranges::LinkedEditingRanges,
    7    scroll::scroll_amount::ScrollAmount,
    8    test::{
    9        assert_text_with_selections, build_editor,
   10        editor_lsp_test_context::{EditorLspTestContext, git_commit_lang},
   11        editor_test_context::EditorTestContext,
   12        select_ranges,
   13    },
   14};
   15use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};
   16use futures::StreamExt;
   17use gpui::{
   18    BackgroundExecutor, DismissEvent, Rgba, SemanticVersion, TestAppContext, UpdateGlobal,
   19    VisualTestContext, WindowBounds, WindowOptions, div,
   20};
   21use indoc::indoc;
   22use language::{
   23    BracketPairConfig,
   24    Capability::ReadWrite,
   25    DiagnosticSourceKind, FakeLspAdapter, IndentGuideSettings, LanguageConfig,
   26    LanguageConfigOverride, LanguageMatcher, LanguageName, Override, Point,
   27    language_settings::{
   28        CompletionSettingsContent, FormatterList, LanguageSettingsContent, LspInsertMode,
   29        SelectedFormatter,
   30    },
   31    tree_sitter_python,
   32};
   33use language_settings::Formatter;
   34use lsp::CompletionParams;
   35use multi_buffer::{IndentGuide, PathKey};
   36use parking_lot::Mutex;
   37use pretty_assertions::{assert_eq, assert_ne};
   38use project::{
   39    FakeFs,
   40    debugger::breakpoint_store::{BreakpointState, SourceBreakpoint},
   41    project_settings::LspSettings,
   42};
   43use serde_json::{self, json};
   44use settings::{
   45    AllLanguageSettingsContent, IndentGuideBackgroundColoring, IndentGuideColoring,
   46    ProjectSettingsContent,
   47};
   48use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant};
   49use std::{
   50    iter,
   51    sync::atomic::{self, AtomicUsize},
   52};
   53use test::{build_editor_with_project, editor_lsp_test_context::rust_lang};
   54use text::ToPoint as _;
   55use unindent::Unindent;
   56use util::{
   57    assert_set_eq, path,
   58    test::{TextRangeMarker, marked_text_ranges, marked_text_ranges_by, sample_text},
   59    uri,
   60};
   61use workspace::{
   62    CloseActiveItem, CloseAllItems, CloseOtherItems, MoveItemToPaneInDirection, NavigationEntry,
   63    OpenOptions, ViewId,
   64    invalid_buffer_view::InvalidBufferView,
   65    item::{FollowEvent, FollowableItem, Item, ItemHandle, SaveOptions},
   66    register_project_item,
   67};
   68
   69#[gpui::test]
   70fn test_edit_events(cx: &mut TestAppContext) {
   71    init_test(cx, |_| {});
   72
   73    let buffer = cx.new(|cx| {
   74        let mut buffer = language::Buffer::local("123456", cx);
   75        buffer.set_group_interval(Duration::from_secs(1));
   76        buffer
   77    });
   78
   79    let events = Rc::new(RefCell::new(Vec::new()));
   80    let editor1 = cx.add_window({
   81        let events = events.clone();
   82        |window, cx| {
   83            let entity = cx.entity();
   84            cx.subscribe_in(
   85                &entity,
   86                window,
   87                move |_, _, event: &EditorEvent, _, _| match event {
   88                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
   89                    EditorEvent::BufferEdited => {
   90                        events.borrow_mut().push(("editor1", "buffer edited"))
   91                    }
   92                    _ => {}
   93                },
   94            )
   95            .detach();
   96            Editor::for_buffer(buffer.clone(), None, window, cx)
   97        }
   98    });
   99
  100    let editor2 = cx.add_window({
  101        let events = events.clone();
  102        |window, cx| {
  103            cx.subscribe_in(
  104                &cx.entity(),
  105                window,
  106                move |_, _, event: &EditorEvent, _, _| match event {
  107                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
  108                    EditorEvent::BufferEdited => {
  109                        events.borrow_mut().push(("editor2", "buffer edited"))
  110                    }
  111                    _ => {}
  112                },
  113            )
  114            .detach();
  115            Editor::for_buffer(buffer.clone(), None, window, cx)
  116        }
  117    });
  118
  119    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  120
  121    // Mutating editor 1 will emit an `Edited` event only for that editor.
  122    _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
  123    assert_eq!(
  124        mem::take(&mut *events.borrow_mut()),
  125        [
  126            ("editor1", "edited"),
  127            ("editor1", "buffer edited"),
  128            ("editor2", "buffer edited"),
  129        ]
  130    );
  131
  132    // Mutating editor 2 will emit an `Edited` event only for that editor.
  133    _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
  134    assert_eq!(
  135        mem::take(&mut *events.borrow_mut()),
  136        [
  137            ("editor2", "edited"),
  138            ("editor1", "buffer edited"),
  139            ("editor2", "buffer edited"),
  140        ]
  141    );
  142
  143    // Undoing on editor 1 will emit an `Edited` event only for that editor.
  144    _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  145    assert_eq!(
  146        mem::take(&mut *events.borrow_mut()),
  147        [
  148            ("editor1", "edited"),
  149            ("editor1", "buffer edited"),
  150            ("editor2", "buffer edited"),
  151        ]
  152    );
  153
  154    // Redoing on editor 1 will emit an `Edited` event only for that editor.
  155    _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  156    assert_eq!(
  157        mem::take(&mut *events.borrow_mut()),
  158        [
  159            ("editor1", "edited"),
  160            ("editor1", "buffer edited"),
  161            ("editor2", "buffer edited"),
  162        ]
  163    );
  164
  165    // Undoing on editor 2 will emit an `Edited` event only for that editor.
  166    _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  167    assert_eq!(
  168        mem::take(&mut *events.borrow_mut()),
  169        [
  170            ("editor2", "edited"),
  171            ("editor1", "buffer edited"),
  172            ("editor2", "buffer edited"),
  173        ]
  174    );
  175
  176    // Redoing on editor 2 will emit an `Edited` event only for that editor.
  177    _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  178    assert_eq!(
  179        mem::take(&mut *events.borrow_mut()),
  180        [
  181            ("editor2", "edited"),
  182            ("editor1", "buffer edited"),
  183            ("editor2", "buffer edited"),
  184        ]
  185    );
  186
  187    // No event is emitted when the mutation is a no-op.
  188    _ = editor2.update(cx, |editor, window, cx| {
  189        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  190            s.select_ranges([0..0])
  191        });
  192
  193        editor.backspace(&Backspace, window, cx);
  194    });
  195    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  196}
  197
  198#[gpui::test]
  199fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
  200    init_test(cx, |_| {});
  201
  202    let mut now = Instant::now();
  203    let group_interval = Duration::from_millis(1);
  204    let buffer = cx.new(|cx| {
  205        let mut buf = language::Buffer::local("123456", cx);
  206        buf.set_group_interval(group_interval);
  207        buf
  208    });
  209    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  210    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
  211
  212    _ = editor.update(cx, |editor, window, cx| {
  213        editor.start_transaction_at(now, window, cx);
  214        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  215            s.select_ranges([2..4])
  216        });
  217
  218        editor.insert("cd", window, cx);
  219        editor.end_transaction_at(now, cx);
  220        assert_eq!(editor.text(cx), "12cd56");
  221        assert_eq!(editor.selections.ranges(cx), vec![4..4]);
  222
  223        editor.start_transaction_at(now, window, cx);
  224        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  225            s.select_ranges([4..5])
  226        });
  227        editor.insert("e", window, cx);
  228        editor.end_transaction_at(now, cx);
  229        assert_eq!(editor.text(cx), "12cde6");
  230        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  231
  232        now += group_interval + Duration::from_millis(1);
  233        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  234            s.select_ranges([2..2])
  235        });
  236
  237        // Simulate an edit in another editor
  238        buffer.update(cx, |buffer, cx| {
  239            buffer.start_transaction_at(now, cx);
  240            buffer.edit([(0..1, "a")], None, cx);
  241            buffer.edit([(1..1, "b")], None, cx);
  242            buffer.end_transaction_at(now, cx);
  243        });
  244
  245        assert_eq!(editor.text(cx), "ab2cde6");
  246        assert_eq!(editor.selections.ranges(cx), vec![3..3]);
  247
  248        // Last transaction happened past the group interval in a different editor.
  249        // Undo it individually and don't restore selections.
  250        editor.undo(&Undo, window, cx);
  251        assert_eq!(editor.text(cx), "12cde6");
  252        assert_eq!(editor.selections.ranges(cx), vec![2..2]);
  253
  254        // First two transactions happened within the group interval in this editor.
  255        // Undo them together and restore selections.
  256        editor.undo(&Undo, window, cx);
  257        editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
  258        assert_eq!(editor.text(cx), "123456");
  259        assert_eq!(editor.selections.ranges(cx), vec![0..0]);
  260
  261        // Redo the first two transactions together.
  262        editor.redo(&Redo, window, cx);
  263        assert_eq!(editor.text(cx), "12cde6");
  264        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  265
  266        // Redo the last transaction on its own.
  267        editor.redo(&Redo, window, cx);
  268        assert_eq!(editor.text(cx), "ab2cde6");
  269        assert_eq!(editor.selections.ranges(cx), vec![6..6]);
  270
  271        // Test empty transactions.
  272        editor.start_transaction_at(now, window, cx);
  273        editor.end_transaction_at(now, cx);
  274        editor.undo(&Undo, window, cx);
  275        assert_eq!(editor.text(cx), "12cde6");
  276    });
  277}
  278
  279#[gpui::test]
  280fn test_ime_composition(cx: &mut TestAppContext) {
  281    init_test(cx, |_| {});
  282
  283    let buffer = cx.new(|cx| {
  284        let mut buffer = language::Buffer::local("abcde", cx);
  285        // Ensure automatic grouping doesn't occur.
  286        buffer.set_group_interval(Duration::ZERO);
  287        buffer
  288    });
  289
  290    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  291    cx.add_window(|window, cx| {
  292        let mut editor = build_editor(buffer.clone(), window, cx);
  293
  294        // Start a new IME composition.
  295        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  296        editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx);
  297        editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
  298        assert_eq!(editor.text(cx), "äbcde");
  299        assert_eq!(
  300            editor.marked_text_ranges(cx),
  301            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  302        );
  303
  304        // Finalize IME composition.
  305        editor.replace_text_in_range(None, "ā", window, cx);
  306        assert_eq!(editor.text(cx), "ābcde");
  307        assert_eq!(editor.marked_text_ranges(cx), None);
  308
  309        // IME composition edits are grouped and are undone/redone at once.
  310        editor.undo(&Default::default(), window, cx);
  311        assert_eq!(editor.text(cx), "abcde");
  312        assert_eq!(editor.marked_text_ranges(cx), None);
  313        editor.redo(&Default::default(), window, cx);
  314        assert_eq!(editor.text(cx), "ābcde");
  315        assert_eq!(editor.marked_text_ranges(cx), None);
  316
  317        // Start a new IME composition.
  318        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  319        assert_eq!(
  320            editor.marked_text_ranges(cx),
  321            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  322        );
  323
  324        // Undoing during an IME composition cancels it.
  325        editor.undo(&Default::default(), window, cx);
  326        assert_eq!(editor.text(cx), "ābcde");
  327        assert_eq!(editor.marked_text_ranges(cx), None);
  328
  329        // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
  330        editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
  331        assert_eq!(editor.text(cx), "ābcdè");
  332        assert_eq!(
  333            editor.marked_text_ranges(cx),
  334            Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
  335        );
  336
  337        // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
  338        editor.replace_text_in_range(Some(4..999), "ę", window, cx);
  339        assert_eq!(editor.text(cx), "ābcdę");
  340        assert_eq!(editor.marked_text_ranges(cx), None);
  341
  342        // Start a new IME composition with multiple cursors.
  343        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  344            s.select_ranges([
  345                OffsetUtf16(1)..OffsetUtf16(1),
  346                OffsetUtf16(3)..OffsetUtf16(3),
  347                OffsetUtf16(5)..OffsetUtf16(5),
  348            ])
  349        });
  350        editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
  351        assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
  352        assert_eq!(
  353            editor.marked_text_ranges(cx),
  354            Some(vec![
  355                OffsetUtf16(0)..OffsetUtf16(3),
  356                OffsetUtf16(4)..OffsetUtf16(7),
  357                OffsetUtf16(8)..OffsetUtf16(11)
  358            ])
  359        );
  360
  361        // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
  362        editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
  363        assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
  364        assert_eq!(
  365            editor.marked_text_ranges(cx),
  366            Some(vec![
  367                OffsetUtf16(1)..OffsetUtf16(2),
  368                OffsetUtf16(5)..OffsetUtf16(6),
  369                OffsetUtf16(9)..OffsetUtf16(10)
  370            ])
  371        );
  372
  373        // Finalize IME composition with multiple cursors.
  374        editor.replace_text_in_range(Some(9..10), "2", window, cx);
  375        assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
  376        assert_eq!(editor.marked_text_ranges(cx), None);
  377
  378        editor
  379    });
  380}
  381
  382#[gpui::test]
  383fn test_selection_with_mouse(cx: &mut TestAppContext) {
  384    init_test(cx, |_| {});
  385
  386    let editor = cx.add_window(|window, cx| {
  387        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  388        build_editor(buffer, window, cx)
  389    });
  390
  391    _ = editor.update(cx, |editor, window, cx| {
  392        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  393    });
  394    assert_eq!(
  395        editor
  396            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  397            .unwrap(),
  398        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  399    );
  400
  401    _ = editor.update(cx, |editor, window, cx| {
  402        editor.update_selection(
  403            DisplayPoint::new(DisplayRow(3), 3),
  404            0,
  405            gpui::Point::<f32>::default(),
  406            window,
  407            cx,
  408        );
  409    });
  410
  411    assert_eq!(
  412        editor
  413            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  414            .unwrap(),
  415        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  416    );
  417
  418    _ = editor.update(cx, |editor, window, cx| {
  419        editor.update_selection(
  420            DisplayPoint::new(DisplayRow(1), 1),
  421            0,
  422            gpui::Point::<f32>::default(),
  423            window,
  424            cx,
  425        );
  426    });
  427
  428    assert_eq!(
  429        editor
  430            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  431            .unwrap(),
  432        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  433    );
  434
  435    _ = editor.update(cx, |editor, window, cx| {
  436        editor.end_selection(window, cx);
  437        editor.update_selection(
  438            DisplayPoint::new(DisplayRow(3), 3),
  439            0,
  440            gpui::Point::<f32>::default(),
  441            window,
  442            cx,
  443        );
  444    });
  445
  446    assert_eq!(
  447        editor
  448            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  449            .unwrap(),
  450        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  451    );
  452
  453    _ = editor.update(cx, |editor, window, cx| {
  454        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
  455        editor.update_selection(
  456            DisplayPoint::new(DisplayRow(0), 0),
  457            0,
  458            gpui::Point::<f32>::default(),
  459            window,
  460            cx,
  461        );
  462    });
  463
  464    assert_eq!(
  465        editor
  466            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  467            .unwrap(),
  468        [
  469            DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
  470            DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
  471        ]
  472    );
  473
  474    _ = editor.update(cx, |editor, window, cx| {
  475        editor.end_selection(window, cx);
  476    });
  477
  478    assert_eq!(
  479        editor
  480            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  481            .unwrap(),
  482        [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
  483    );
  484}
  485
  486#[gpui::test]
  487fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
  488    init_test(cx, |_| {});
  489
  490    let editor = cx.add_window(|window, cx| {
  491        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  492        build_editor(buffer, window, cx)
  493    });
  494
  495    _ = editor.update(cx, |editor, window, cx| {
  496        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
  497    });
  498
  499    _ = editor.update(cx, |editor, window, cx| {
  500        editor.end_selection(window, cx);
  501    });
  502
  503    _ = editor.update(cx, |editor, window, cx| {
  504        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
  505    });
  506
  507    _ = editor.update(cx, |editor, window, cx| {
  508        editor.end_selection(window, cx);
  509    });
  510
  511    assert_eq!(
  512        editor
  513            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  514            .unwrap(),
  515        [
  516            DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
  517            DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
  518        ]
  519    );
  520
  521    _ = editor.update(cx, |editor, window, cx| {
  522        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
  523    });
  524
  525    _ = editor.update(cx, |editor, window, cx| {
  526        editor.end_selection(window, cx);
  527    });
  528
  529    assert_eq!(
  530        editor
  531            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  532            .unwrap(),
  533        [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  534    );
  535}
  536
  537#[gpui::test]
  538fn test_canceling_pending_selection(cx: &mut TestAppContext) {
  539    init_test(cx, |_| {});
  540
  541    let editor = cx.add_window(|window, cx| {
  542        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  543        build_editor(buffer, window, cx)
  544    });
  545
  546    _ = editor.update(cx, |editor, window, cx| {
  547        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  548        assert_eq!(
  549            editor.selections.display_ranges(cx),
  550            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  551        );
  552    });
  553
  554    _ = editor.update(cx, |editor, window, cx| {
  555        editor.update_selection(
  556            DisplayPoint::new(DisplayRow(3), 3),
  557            0,
  558            gpui::Point::<f32>::default(),
  559            window,
  560            cx,
  561        );
  562        assert_eq!(
  563            editor.selections.display_ranges(cx),
  564            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  565        );
  566    });
  567
  568    _ = editor.update(cx, |editor, window, cx| {
  569        editor.cancel(&Cancel, window, cx);
  570        editor.update_selection(
  571            DisplayPoint::new(DisplayRow(1), 1),
  572            0,
  573            gpui::Point::<f32>::default(),
  574            window,
  575            cx,
  576        );
  577        assert_eq!(
  578            editor.selections.display_ranges(cx),
  579            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  580        );
  581    });
  582}
  583
  584#[gpui::test]
  585fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
  586    init_test(cx, |_| {});
  587
  588    let editor = cx.add_window(|window, cx| {
  589        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  590        build_editor(buffer, window, cx)
  591    });
  592
  593    _ = editor.update(cx, |editor, window, cx| {
  594        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  595        assert_eq!(
  596            editor.selections.display_ranges(cx),
  597            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  598        );
  599
  600        editor.move_down(&Default::default(), window, cx);
  601        assert_eq!(
  602            editor.selections.display_ranges(cx),
  603            [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  604        );
  605
  606        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  607        assert_eq!(
  608            editor.selections.display_ranges(cx),
  609            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  610        );
  611
  612        editor.move_up(&Default::default(), window, cx);
  613        assert_eq!(
  614            editor.selections.display_ranges(cx),
  615            [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
  616        );
  617    });
  618}
  619
  620#[gpui::test]
  621fn test_clone(cx: &mut TestAppContext) {
  622    init_test(cx, |_| {});
  623
  624    let (text, selection_ranges) = marked_text_ranges(
  625        indoc! {"
  626            one
  627            two
  628            threeˇ
  629            four
  630            fiveˇ
  631        "},
  632        true,
  633    );
  634
  635    let editor = cx.add_window(|window, cx| {
  636        let buffer = MultiBuffer::build_simple(&text, cx);
  637        build_editor(buffer, window, cx)
  638    });
  639
  640    _ = editor.update(cx, |editor, window, cx| {
  641        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  642            s.select_ranges(selection_ranges.clone())
  643        });
  644        editor.fold_creases(
  645            vec![
  646                Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
  647                Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
  648            ],
  649            true,
  650            window,
  651            cx,
  652        );
  653    });
  654
  655    let cloned_editor = editor
  656        .update(cx, |editor, _, cx| {
  657            cx.open_window(Default::default(), |window, cx| {
  658                cx.new(|cx| editor.clone(window, cx))
  659            })
  660        })
  661        .unwrap()
  662        .unwrap();
  663
  664    let snapshot = editor
  665        .update(cx, |e, window, cx| e.snapshot(window, cx))
  666        .unwrap();
  667    let cloned_snapshot = cloned_editor
  668        .update(cx, |e, window, cx| e.snapshot(window, cx))
  669        .unwrap();
  670
  671    assert_eq!(
  672        cloned_editor
  673            .update(cx, |e, _, cx| e.display_text(cx))
  674            .unwrap(),
  675        editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
  676    );
  677    assert_eq!(
  678        cloned_snapshot
  679            .folds_in_range(0..text.len())
  680            .collect::<Vec<_>>(),
  681        snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
  682    );
  683    assert_set_eq!(
  684        cloned_editor
  685            .update(cx, |editor, _, cx| editor.selections.ranges::<Point>(cx))
  686            .unwrap(),
  687        editor
  688            .update(cx, |editor, _, cx| editor.selections.ranges(cx))
  689            .unwrap()
  690    );
  691    assert_set_eq!(
  692        cloned_editor
  693            .update(cx, |e, _window, cx| e.selections.display_ranges(cx))
  694            .unwrap(),
  695        editor
  696            .update(cx, |e, _, cx| e.selections.display_ranges(cx))
  697            .unwrap()
  698    );
  699}
  700
  701#[gpui::test]
  702async fn test_navigation_history(cx: &mut TestAppContext) {
  703    init_test(cx, |_| {});
  704
  705    use workspace::item::Item;
  706
  707    let fs = FakeFs::new(cx.executor());
  708    let project = Project::test(fs, [], cx).await;
  709    let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
  710    let pane = workspace
  711        .update(cx, |workspace, _, _| workspace.active_pane().clone())
  712        .unwrap();
  713
  714    _ = workspace.update(cx, |_v, window, cx| {
  715        cx.new(|cx| {
  716            let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
  717            let mut editor = build_editor(buffer, window, cx);
  718            let handle = cx.entity();
  719            editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
  720
  721            fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
  722                editor.nav_history.as_mut().unwrap().pop_backward(cx)
  723            }
  724
  725            // Move the cursor a small distance.
  726            // Nothing is added to the navigation history.
  727            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  728                s.select_display_ranges([
  729                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
  730                ])
  731            });
  732            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  733                s.select_display_ranges([
  734                    DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
  735                ])
  736            });
  737            assert!(pop_history(&mut editor, cx).is_none());
  738
  739            // Move the cursor a large distance.
  740            // The history can jump back to the previous position.
  741            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  742                s.select_display_ranges([
  743                    DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
  744                ])
  745            });
  746            let nav_entry = pop_history(&mut editor, cx).unwrap();
  747            editor.navigate(nav_entry.data.unwrap(), window, cx);
  748            assert_eq!(nav_entry.item.id(), cx.entity_id());
  749            assert_eq!(
  750                editor.selections.display_ranges(cx),
  751                &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
  752            );
  753            assert!(pop_history(&mut editor, cx).is_none());
  754
  755            // Move the cursor a small distance via the mouse.
  756            // Nothing is added to the navigation history.
  757            editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
  758            editor.end_selection(window, cx);
  759            assert_eq!(
  760                editor.selections.display_ranges(cx),
  761                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  762            );
  763            assert!(pop_history(&mut editor, cx).is_none());
  764
  765            // Move the cursor a large distance via the mouse.
  766            // The history can jump back to the previous position.
  767            editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
  768            editor.end_selection(window, cx);
  769            assert_eq!(
  770                editor.selections.display_ranges(cx),
  771                &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
  772            );
  773            let nav_entry = pop_history(&mut editor, cx).unwrap();
  774            editor.navigate(nav_entry.data.unwrap(), window, cx);
  775            assert_eq!(nav_entry.item.id(), cx.entity_id());
  776            assert_eq!(
  777                editor.selections.display_ranges(cx),
  778                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  779            );
  780            assert!(pop_history(&mut editor, cx).is_none());
  781
  782            // Set scroll position to check later
  783            editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), window, cx);
  784            let original_scroll_position = editor.scroll_manager.anchor();
  785
  786            // Jump to the end of the document and adjust scroll
  787            editor.move_to_end(&MoveToEnd, window, cx);
  788            editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), window, cx);
  789            assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
  790
  791            let nav_entry = pop_history(&mut editor, cx).unwrap();
  792            editor.navigate(nav_entry.data.unwrap(), window, cx);
  793            assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
  794
  795            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
  796            let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
  797            invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
  798            let invalid_point = Point::new(9999, 0);
  799            editor.navigate(
  800                Box::new(NavigationData {
  801                    cursor_anchor: invalid_anchor,
  802                    cursor_position: invalid_point,
  803                    scroll_anchor: ScrollAnchor {
  804                        anchor: invalid_anchor,
  805                        offset: Default::default(),
  806                    },
  807                    scroll_top_row: invalid_point.row,
  808                }),
  809                window,
  810                cx,
  811            );
  812            assert_eq!(
  813                editor.selections.display_ranges(cx),
  814                &[editor.max_point(cx)..editor.max_point(cx)]
  815            );
  816            assert_eq!(
  817                editor.scroll_position(cx),
  818                gpui::Point::new(0., editor.max_point(cx).row().as_f32())
  819            );
  820
  821            editor
  822        })
  823    });
  824}
  825
  826#[gpui::test]
  827fn test_cancel(cx: &mut TestAppContext) {
  828    init_test(cx, |_| {});
  829
  830    let editor = cx.add_window(|window, cx| {
  831        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  832        build_editor(buffer, window, cx)
  833    });
  834
  835    _ = editor.update(cx, |editor, window, cx| {
  836        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
  837        editor.update_selection(
  838            DisplayPoint::new(DisplayRow(1), 1),
  839            0,
  840            gpui::Point::<f32>::default(),
  841            window,
  842            cx,
  843        );
  844        editor.end_selection(window, cx);
  845
  846        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
  847        editor.update_selection(
  848            DisplayPoint::new(DisplayRow(0), 3),
  849            0,
  850            gpui::Point::<f32>::default(),
  851            window,
  852            cx,
  853        );
  854        editor.end_selection(window, cx);
  855        assert_eq!(
  856            editor.selections.display_ranges(cx),
  857            [
  858                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
  859                DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
  860            ]
  861        );
  862    });
  863
  864    _ = editor.update(cx, |editor, window, cx| {
  865        editor.cancel(&Cancel, window, cx);
  866        assert_eq!(
  867            editor.selections.display_ranges(cx),
  868            [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
  869        );
  870    });
  871
  872    _ = editor.update(cx, |editor, window, cx| {
  873        editor.cancel(&Cancel, window, cx);
  874        assert_eq!(
  875            editor.selections.display_ranges(cx),
  876            [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
  877        );
  878    });
  879}
  880
  881#[gpui::test]
  882fn test_fold_action(cx: &mut TestAppContext) {
  883    init_test(cx, |_| {});
  884
  885    let editor = cx.add_window(|window, cx| {
  886        let buffer = MultiBuffer::build_simple(
  887            &"
  888                impl Foo {
  889                    // Hello!
  890
  891                    fn a() {
  892                        1
  893                    }
  894
  895                    fn b() {
  896                        2
  897                    }
  898
  899                    fn c() {
  900                        3
  901                    }
  902                }
  903            "
  904            .unindent(),
  905            cx,
  906        );
  907        build_editor(buffer, window, cx)
  908    });
  909
  910    _ = editor.update(cx, |editor, window, cx| {
  911        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  912            s.select_display_ranges([
  913                DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
  914            ]);
  915        });
  916        editor.fold(&Fold, window, cx);
  917        assert_eq!(
  918            editor.display_text(cx),
  919            "
  920                impl Foo {
  921                    // Hello!
  922
  923                    fn a() {
  924                        1
  925                    }
  926
  927                    fn b() {⋯
  928                    }
  929
  930                    fn c() {⋯
  931                    }
  932                }
  933            "
  934            .unindent(),
  935        );
  936
  937        editor.fold(&Fold, window, cx);
  938        assert_eq!(
  939            editor.display_text(cx),
  940            "
  941                impl Foo {⋯
  942                }
  943            "
  944            .unindent(),
  945        );
  946
  947        editor.unfold_lines(&UnfoldLines, window, cx);
  948        assert_eq!(
  949            editor.display_text(cx),
  950            "
  951                impl Foo {
  952                    // Hello!
  953
  954                    fn a() {
  955                        1
  956                    }
  957
  958                    fn b() {⋯
  959                    }
  960
  961                    fn c() {⋯
  962                    }
  963                }
  964            "
  965            .unindent(),
  966        );
  967
  968        editor.unfold_lines(&UnfoldLines, window, cx);
  969        assert_eq!(
  970            editor.display_text(cx),
  971            editor.buffer.read(cx).read(cx).text()
  972        );
  973    });
  974}
  975
  976#[gpui::test]
  977fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
  978    init_test(cx, |_| {});
  979
  980    let editor = cx.add_window(|window, cx| {
  981        let buffer = MultiBuffer::build_simple(
  982            &"
  983                class Foo:
  984                    # Hello!
  985
  986                    def a():
  987                        print(1)
  988
  989                    def b():
  990                        print(2)
  991
  992                    def c():
  993                        print(3)
  994            "
  995            .unindent(),
  996            cx,
  997        );
  998        build_editor(buffer, window, cx)
  999    });
 1000
 1001    _ = editor.update(cx, |editor, window, cx| {
 1002        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1003            s.select_display_ranges([
 1004                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
 1005            ]);
 1006        });
 1007        editor.fold(&Fold, window, cx);
 1008        assert_eq!(
 1009            editor.display_text(cx),
 1010            "
 1011                class Foo:
 1012                    # Hello!
 1013
 1014                    def a():
 1015                        print(1)
 1016
 1017                    def b():⋯
 1018
 1019                    def c():⋯
 1020            "
 1021            .unindent(),
 1022        );
 1023
 1024        editor.fold(&Fold, window, cx);
 1025        assert_eq!(
 1026            editor.display_text(cx),
 1027            "
 1028                class Foo:⋯
 1029            "
 1030            .unindent(),
 1031        );
 1032
 1033        editor.unfold_lines(&UnfoldLines, window, cx);
 1034        assert_eq!(
 1035            editor.display_text(cx),
 1036            "
 1037                class Foo:
 1038                    # Hello!
 1039
 1040                    def a():
 1041                        print(1)
 1042
 1043                    def b():⋯
 1044
 1045                    def c():⋯
 1046            "
 1047            .unindent(),
 1048        );
 1049
 1050        editor.unfold_lines(&UnfoldLines, window, cx);
 1051        assert_eq!(
 1052            editor.display_text(cx),
 1053            editor.buffer.read(cx).read(cx).text()
 1054        );
 1055    });
 1056}
 1057
 1058#[gpui::test]
 1059fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
 1060    init_test(cx, |_| {});
 1061
 1062    let editor = cx.add_window(|window, cx| {
 1063        let buffer = MultiBuffer::build_simple(
 1064            &"
 1065                class Foo:
 1066                    # Hello!
 1067
 1068                    def a():
 1069                        print(1)
 1070
 1071                    def b():
 1072                        print(2)
 1073
 1074
 1075                    def c():
 1076                        print(3)
 1077
 1078
 1079            "
 1080            .unindent(),
 1081            cx,
 1082        );
 1083        build_editor(buffer, window, cx)
 1084    });
 1085
 1086    _ = editor.update(cx, |editor, window, cx| {
 1087        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1088            s.select_display_ranges([
 1089                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
 1090            ]);
 1091        });
 1092        editor.fold(&Fold, window, cx);
 1093        assert_eq!(
 1094            editor.display_text(cx),
 1095            "
 1096                class Foo:
 1097                    # Hello!
 1098
 1099                    def a():
 1100                        print(1)
 1101
 1102                    def b():⋯
 1103
 1104
 1105                    def c():⋯
 1106
 1107
 1108            "
 1109            .unindent(),
 1110        );
 1111
 1112        editor.fold(&Fold, window, cx);
 1113        assert_eq!(
 1114            editor.display_text(cx),
 1115            "
 1116                class Foo:⋯
 1117
 1118
 1119            "
 1120            .unindent(),
 1121        );
 1122
 1123        editor.unfold_lines(&UnfoldLines, window, cx);
 1124        assert_eq!(
 1125            editor.display_text(cx),
 1126            "
 1127                class Foo:
 1128                    # Hello!
 1129
 1130                    def a():
 1131                        print(1)
 1132
 1133                    def b():⋯
 1134
 1135
 1136                    def c():⋯
 1137
 1138
 1139            "
 1140            .unindent(),
 1141        );
 1142
 1143        editor.unfold_lines(&UnfoldLines, window, cx);
 1144        assert_eq!(
 1145            editor.display_text(cx),
 1146            editor.buffer.read(cx).read(cx).text()
 1147        );
 1148    });
 1149}
 1150
 1151#[gpui::test]
 1152fn test_fold_at_level(cx: &mut TestAppContext) {
 1153    init_test(cx, |_| {});
 1154
 1155    let editor = cx.add_window(|window, cx| {
 1156        let buffer = MultiBuffer::build_simple(
 1157            &"
 1158                class Foo:
 1159                    # Hello!
 1160
 1161                    def a():
 1162                        print(1)
 1163
 1164                    def b():
 1165                        print(2)
 1166
 1167
 1168                class Bar:
 1169                    # World!
 1170
 1171                    def a():
 1172                        print(1)
 1173
 1174                    def b():
 1175                        print(2)
 1176
 1177
 1178            "
 1179            .unindent(),
 1180            cx,
 1181        );
 1182        build_editor(buffer, window, cx)
 1183    });
 1184
 1185    _ = editor.update(cx, |editor, window, cx| {
 1186        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1187        assert_eq!(
 1188            editor.display_text(cx),
 1189            "
 1190                class Foo:
 1191                    # Hello!
 1192
 1193                    def a():⋯
 1194
 1195                    def b():⋯
 1196
 1197
 1198                class Bar:
 1199                    # World!
 1200
 1201                    def a():⋯
 1202
 1203                    def b():⋯
 1204
 1205
 1206            "
 1207            .unindent(),
 1208        );
 1209
 1210        editor.fold_at_level(&FoldAtLevel(1), window, cx);
 1211        assert_eq!(
 1212            editor.display_text(cx),
 1213            "
 1214                class Foo:⋯
 1215
 1216
 1217                class Bar:⋯
 1218
 1219
 1220            "
 1221            .unindent(),
 1222        );
 1223
 1224        editor.unfold_all(&UnfoldAll, window, cx);
 1225        editor.fold_at_level(&FoldAtLevel(0), window, cx);
 1226        assert_eq!(
 1227            editor.display_text(cx),
 1228            "
 1229                class Foo:
 1230                    # Hello!
 1231
 1232                    def a():
 1233                        print(1)
 1234
 1235                    def b():
 1236                        print(2)
 1237
 1238
 1239                class Bar:
 1240                    # World!
 1241
 1242                    def a():
 1243                        print(1)
 1244
 1245                    def b():
 1246                        print(2)
 1247
 1248
 1249            "
 1250            .unindent(),
 1251        );
 1252
 1253        assert_eq!(
 1254            editor.display_text(cx),
 1255            editor.buffer.read(cx).read(cx).text()
 1256        );
 1257    });
 1258}
 1259
 1260#[gpui::test]
 1261fn test_move_cursor(cx: &mut TestAppContext) {
 1262    init_test(cx, |_| {});
 1263
 1264    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
 1265    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
 1266
 1267    buffer.update(cx, |buffer, cx| {
 1268        buffer.edit(
 1269            vec![
 1270                (Point::new(1, 0)..Point::new(1, 0), "\t"),
 1271                (Point::new(1, 1)..Point::new(1, 1), "\t"),
 1272            ],
 1273            None,
 1274            cx,
 1275        );
 1276    });
 1277    _ = editor.update(cx, |editor, window, cx| {
 1278        assert_eq!(
 1279            editor.selections.display_ranges(cx),
 1280            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1281        );
 1282
 1283        editor.move_down(&MoveDown, window, cx);
 1284        assert_eq!(
 1285            editor.selections.display_ranges(cx),
 1286            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1287        );
 1288
 1289        editor.move_right(&MoveRight, window, cx);
 1290        assert_eq!(
 1291            editor.selections.display_ranges(cx),
 1292            &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
 1293        );
 1294
 1295        editor.move_left(&MoveLeft, window, cx);
 1296        assert_eq!(
 1297            editor.selections.display_ranges(cx),
 1298            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1299        );
 1300
 1301        editor.move_up(&MoveUp, window, cx);
 1302        assert_eq!(
 1303            editor.selections.display_ranges(cx),
 1304            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1305        );
 1306
 1307        editor.move_to_end(&MoveToEnd, window, cx);
 1308        assert_eq!(
 1309            editor.selections.display_ranges(cx),
 1310            &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
 1311        );
 1312
 1313        editor.move_to_beginning(&MoveToBeginning, window, cx);
 1314        assert_eq!(
 1315            editor.selections.display_ranges(cx),
 1316            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1317        );
 1318
 1319        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1320            s.select_display_ranges([
 1321                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
 1322            ]);
 1323        });
 1324        editor.select_to_beginning(&SelectToBeginning, window, cx);
 1325        assert_eq!(
 1326            editor.selections.display_ranges(cx),
 1327            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
 1328        );
 1329
 1330        editor.select_to_end(&SelectToEnd, window, cx);
 1331        assert_eq!(
 1332            editor.selections.display_ranges(cx),
 1333            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
 1334        );
 1335    });
 1336}
 1337
 1338#[gpui::test]
 1339fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 1340    init_test(cx, |_| {});
 1341
 1342    let editor = cx.add_window(|window, cx| {
 1343        let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
 1344        build_editor(buffer, window, cx)
 1345    });
 1346
 1347    assert_eq!('🟥'.len_utf8(), 4);
 1348    assert_eq!('α'.len_utf8(), 2);
 1349
 1350    _ = editor.update(cx, |editor, window, cx| {
 1351        editor.fold_creases(
 1352            vec![
 1353                Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
 1354                Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
 1355                Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
 1356            ],
 1357            true,
 1358            window,
 1359            cx,
 1360        );
 1361        assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
 1362
 1363        editor.move_right(&MoveRight, window, cx);
 1364        assert_eq!(
 1365            editor.selections.display_ranges(cx),
 1366            &[empty_range(0, "🟥".len())]
 1367        );
 1368        editor.move_right(&MoveRight, window, cx);
 1369        assert_eq!(
 1370            editor.selections.display_ranges(cx),
 1371            &[empty_range(0, "🟥🟧".len())]
 1372        );
 1373        editor.move_right(&MoveRight, window, cx);
 1374        assert_eq!(
 1375            editor.selections.display_ranges(cx),
 1376            &[empty_range(0, "🟥🟧⋯".len())]
 1377        );
 1378
 1379        editor.move_down(&MoveDown, window, cx);
 1380        assert_eq!(
 1381            editor.selections.display_ranges(cx),
 1382            &[empty_range(1, "ab⋯e".len())]
 1383        );
 1384        editor.move_left(&MoveLeft, window, cx);
 1385        assert_eq!(
 1386            editor.selections.display_ranges(cx),
 1387            &[empty_range(1, "ab⋯".len())]
 1388        );
 1389        editor.move_left(&MoveLeft, window, cx);
 1390        assert_eq!(
 1391            editor.selections.display_ranges(cx),
 1392            &[empty_range(1, "ab".len())]
 1393        );
 1394        editor.move_left(&MoveLeft, window, cx);
 1395        assert_eq!(
 1396            editor.selections.display_ranges(cx),
 1397            &[empty_range(1, "a".len())]
 1398        );
 1399
 1400        editor.move_down(&MoveDown, window, cx);
 1401        assert_eq!(
 1402            editor.selections.display_ranges(cx),
 1403            &[empty_range(2, "α".len())]
 1404        );
 1405        editor.move_right(&MoveRight, window, cx);
 1406        assert_eq!(
 1407            editor.selections.display_ranges(cx),
 1408            &[empty_range(2, "αβ".len())]
 1409        );
 1410        editor.move_right(&MoveRight, window, cx);
 1411        assert_eq!(
 1412            editor.selections.display_ranges(cx),
 1413            &[empty_range(2, "αβ⋯".len())]
 1414        );
 1415        editor.move_right(&MoveRight, window, cx);
 1416        assert_eq!(
 1417            editor.selections.display_ranges(cx),
 1418            &[empty_range(2, "αβ⋯ε".len())]
 1419        );
 1420
 1421        editor.move_up(&MoveUp, window, cx);
 1422        assert_eq!(
 1423            editor.selections.display_ranges(cx),
 1424            &[empty_range(1, "ab⋯e".len())]
 1425        );
 1426        editor.move_down(&MoveDown, window, cx);
 1427        assert_eq!(
 1428            editor.selections.display_ranges(cx),
 1429            &[empty_range(2, "αβ⋯ε".len())]
 1430        );
 1431        editor.move_up(&MoveUp, window, cx);
 1432        assert_eq!(
 1433            editor.selections.display_ranges(cx),
 1434            &[empty_range(1, "ab⋯e".len())]
 1435        );
 1436
 1437        editor.move_up(&MoveUp, window, cx);
 1438        assert_eq!(
 1439            editor.selections.display_ranges(cx),
 1440            &[empty_range(0, "🟥🟧".len())]
 1441        );
 1442        editor.move_left(&MoveLeft, window, cx);
 1443        assert_eq!(
 1444            editor.selections.display_ranges(cx),
 1445            &[empty_range(0, "🟥".len())]
 1446        );
 1447        editor.move_left(&MoveLeft, window, cx);
 1448        assert_eq!(
 1449            editor.selections.display_ranges(cx),
 1450            &[empty_range(0, "".len())]
 1451        );
 1452    });
 1453}
 1454
 1455#[gpui::test]
 1456fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
 1457    init_test(cx, |_| {});
 1458
 1459    let editor = cx.add_window(|window, cx| {
 1460        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
 1461        build_editor(buffer, window, cx)
 1462    });
 1463    _ = editor.update(cx, |editor, window, cx| {
 1464        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1465            s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
 1466        });
 1467
 1468        // moving above start of document should move selection to start of document,
 1469        // but the next move down should still be at the original goal_x
 1470        editor.move_up(&MoveUp, window, cx);
 1471        assert_eq!(
 1472            editor.selections.display_ranges(cx),
 1473            &[empty_range(0, "".len())]
 1474        );
 1475
 1476        editor.move_down(&MoveDown, window, cx);
 1477        assert_eq!(
 1478            editor.selections.display_ranges(cx),
 1479            &[empty_range(1, "abcd".len())]
 1480        );
 1481
 1482        editor.move_down(&MoveDown, window, cx);
 1483        assert_eq!(
 1484            editor.selections.display_ranges(cx),
 1485            &[empty_range(2, "αβγ".len())]
 1486        );
 1487
 1488        editor.move_down(&MoveDown, window, cx);
 1489        assert_eq!(
 1490            editor.selections.display_ranges(cx),
 1491            &[empty_range(3, "abcd".len())]
 1492        );
 1493
 1494        editor.move_down(&MoveDown, window, cx);
 1495        assert_eq!(
 1496            editor.selections.display_ranges(cx),
 1497            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1498        );
 1499
 1500        // moving past end of document should not change goal_x
 1501        editor.move_down(&MoveDown, window, cx);
 1502        assert_eq!(
 1503            editor.selections.display_ranges(cx),
 1504            &[empty_range(5, "".len())]
 1505        );
 1506
 1507        editor.move_down(&MoveDown, window, cx);
 1508        assert_eq!(
 1509            editor.selections.display_ranges(cx),
 1510            &[empty_range(5, "".len())]
 1511        );
 1512
 1513        editor.move_up(&MoveUp, window, cx);
 1514        assert_eq!(
 1515            editor.selections.display_ranges(cx),
 1516            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1517        );
 1518
 1519        editor.move_up(&MoveUp, window, cx);
 1520        assert_eq!(
 1521            editor.selections.display_ranges(cx),
 1522            &[empty_range(3, "abcd".len())]
 1523        );
 1524
 1525        editor.move_up(&MoveUp, window, cx);
 1526        assert_eq!(
 1527            editor.selections.display_ranges(cx),
 1528            &[empty_range(2, "αβγ".len())]
 1529        );
 1530    });
 1531}
 1532
 1533#[gpui::test]
 1534fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 1535    init_test(cx, |_| {});
 1536    let move_to_beg = MoveToBeginningOfLine {
 1537        stop_at_soft_wraps: true,
 1538        stop_at_indent: true,
 1539    };
 1540
 1541    let delete_to_beg = DeleteToBeginningOfLine {
 1542        stop_at_indent: false,
 1543    };
 1544
 1545    let move_to_end = MoveToEndOfLine {
 1546        stop_at_soft_wraps: true,
 1547    };
 1548
 1549    let editor = cx.add_window(|window, cx| {
 1550        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1551        build_editor(buffer, window, cx)
 1552    });
 1553    _ = editor.update(cx, |editor, window, cx| {
 1554        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1555            s.select_display_ranges([
 1556                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1557                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1558            ]);
 1559        });
 1560    });
 1561
 1562    _ = editor.update(cx, |editor, window, cx| {
 1563        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1564        assert_eq!(
 1565            editor.selections.display_ranges(cx),
 1566            &[
 1567                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1568                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1569            ]
 1570        );
 1571    });
 1572
 1573    _ = editor.update(cx, |editor, window, cx| {
 1574        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1575        assert_eq!(
 1576            editor.selections.display_ranges(cx),
 1577            &[
 1578                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1579                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1580            ]
 1581        );
 1582    });
 1583
 1584    _ = editor.update(cx, |editor, window, cx| {
 1585        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1586        assert_eq!(
 1587            editor.selections.display_ranges(cx),
 1588            &[
 1589                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1590                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1591            ]
 1592        );
 1593    });
 1594
 1595    _ = editor.update(cx, |editor, window, cx| {
 1596        editor.move_to_end_of_line(&move_to_end, window, cx);
 1597        assert_eq!(
 1598            editor.selections.display_ranges(cx),
 1599            &[
 1600                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1601                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1602            ]
 1603        );
 1604    });
 1605
 1606    // Moving to the end of line again is a no-op.
 1607    _ = editor.update(cx, |editor, window, cx| {
 1608        editor.move_to_end_of_line(&move_to_end, window, cx);
 1609        assert_eq!(
 1610            editor.selections.display_ranges(cx),
 1611            &[
 1612                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1613                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1614            ]
 1615        );
 1616    });
 1617
 1618    _ = editor.update(cx, |editor, window, cx| {
 1619        editor.move_left(&MoveLeft, window, cx);
 1620        editor.select_to_beginning_of_line(
 1621            &SelectToBeginningOfLine {
 1622                stop_at_soft_wraps: true,
 1623                stop_at_indent: true,
 1624            },
 1625            window,
 1626            cx,
 1627        );
 1628        assert_eq!(
 1629            editor.selections.display_ranges(cx),
 1630            &[
 1631                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1632                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1633            ]
 1634        );
 1635    });
 1636
 1637    _ = editor.update(cx, |editor, window, cx| {
 1638        editor.select_to_beginning_of_line(
 1639            &SelectToBeginningOfLine {
 1640                stop_at_soft_wraps: true,
 1641                stop_at_indent: true,
 1642            },
 1643            window,
 1644            cx,
 1645        );
 1646        assert_eq!(
 1647            editor.selections.display_ranges(cx),
 1648            &[
 1649                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1650                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1651            ]
 1652        );
 1653    });
 1654
 1655    _ = editor.update(cx, |editor, window, cx| {
 1656        editor.select_to_beginning_of_line(
 1657            &SelectToBeginningOfLine {
 1658                stop_at_soft_wraps: true,
 1659                stop_at_indent: true,
 1660            },
 1661            window,
 1662            cx,
 1663        );
 1664        assert_eq!(
 1665            editor.selections.display_ranges(cx),
 1666            &[
 1667                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1668                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1669            ]
 1670        );
 1671    });
 1672
 1673    _ = editor.update(cx, |editor, window, cx| {
 1674        editor.select_to_end_of_line(
 1675            &SelectToEndOfLine {
 1676                stop_at_soft_wraps: true,
 1677            },
 1678            window,
 1679            cx,
 1680        );
 1681        assert_eq!(
 1682            editor.selections.display_ranges(cx),
 1683            &[
 1684                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
 1685                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
 1686            ]
 1687        );
 1688    });
 1689
 1690    _ = editor.update(cx, |editor, window, cx| {
 1691        editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
 1692        assert_eq!(editor.display_text(cx), "ab\n  de");
 1693        assert_eq!(
 1694            editor.selections.display_ranges(cx),
 1695            &[
 1696                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 1697                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1698            ]
 1699        );
 1700    });
 1701
 1702    _ = editor.update(cx, |editor, window, cx| {
 1703        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1704        assert_eq!(editor.display_text(cx), "\n");
 1705        assert_eq!(
 1706            editor.selections.display_ranges(cx),
 1707            &[
 1708                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1709                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1710            ]
 1711        );
 1712    });
 1713}
 1714
 1715#[gpui::test]
 1716fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
 1717    init_test(cx, |_| {});
 1718    let move_to_beg = MoveToBeginningOfLine {
 1719        stop_at_soft_wraps: false,
 1720        stop_at_indent: false,
 1721    };
 1722
 1723    let move_to_end = MoveToEndOfLine {
 1724        stop_at_soft_wraps: false,
 1725    };
 1726
 1727    let editor = cx.add_window(|window, cx| {
 1728        let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
 1729        build_editor(buffer, window, cx)
 1730    });
 1731
 1732    _ = editor.update(cx, |editor, window, cx| {
 1733        editor.set_wrap_width(Some(140.0.into()), cx);
 1734
 1735        // We expect the following lines after wrapping
 1736        // ```
 1737        // thequickbrownfox
 1738        // jumpedoverthelazydo
 1739        // gs
 1740        // ```
 1741        // The final `gs` was soft-wrapped onto a new line.
 1742        assert_eq!(
 1743            "thequickbrownfox\njumpedoverthelaz\nydogs",
 1744            editor.display_text(cx),
 1745        );
 1746
 1747        // First, let's assert behavior on the first line, that was not soft-wrapped.
 1748        // Start the cursor at the `k` on the first line
 1749        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1750            s.select_display_ranges([
 1751                DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
 1752            ]);
 1753        });
 1754
 1755        // Moving to the beginning of the line should put us at the beginning of the line.
 1756        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1757        assert_eq!(
 1758            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
 1759            editor.selections.display_ranges(cx)
 1760        );
 1761
 1762        // Moving to the end of the line should put us at the end of the line.
 1763        editor.move_to_end_of_line(&move_to_end, window, cx);
 1764        assert_eq!(
 1765            vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
 1766            editor.selections.display_ranges(cx)
 1767        );
 1768
 1769        // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
 1770        // Start the cursor at the last line (`y` that was wrapped to a new line)
 1771        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1772            s.select_display_ranges([
 1773                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
 1774            ]);
 1775        });
 1776
 1777        // Moving to the beginning of the line should put us at the start of the second line of
 1778        // display text, i.e., the `j`.
 1779        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1780        assert_eq!(
 1781            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1782            editor.selections.display_ranges(cx)
 1783        );
 1784
 1785        // Moving to the beginning of the line again should be a no-op.
 1786        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1787        assert_eq!(
 1788            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1789            editor.selections.display_ranges(cx)
 1790        );
 1791
 1792        // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
 1793        // next display line.
 1794        editor.move_to_end_of_line(&move_to_end, window, cx);
 1795        assert_eq!(
 1796            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1797            editor.selections.display_ranges(cx)
 1798        );
 1799
 1800        // Moving to the end of the line again should be a no-op.
 1801        editor.move_to_end_of_line(&move_to_end, window, cx);
 1802        assert_eq!(
 1803            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1804            editor.selections.display_ranges(cx)
 1805        );
 1806    });
 1807}
 1808
 1809#[gpui::test]
 1810fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
 1811    init_test(cx, |_| {});
 1812
 1813    let move_to_beg = MoveToBeginningOfLine {
 1814        stop_at_soft_wraps: true,
 1815        stop_at_indent: true,
 1816    };
 1817
 1818    let select_to_beg = SelectToBeginningOfLine {
 1819        stop_at_soft_wraps: true,
 1820        stop_at_indent: true,
 1821    };
 1822
 1823    let delete_to_beg = DeleteToBeginningOfLine {
 1824        stop_at_indent: true,
 1825    };
 1826
 1827    let move_to_end = MoveToEndOfLine {
 1828        stop_at_soft_wraps: false,
 1829    };
 1830
 1831    let editor = cx.add_window(|window, cx| {
 1832        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1833        build_editor(buffer, window, cx)
 1834    });
 1835
 1836    _ = editor.update(cx, |editor, window, cx| {
 1837        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1838            s.select_display_ranges([
 1839                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1840                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1841            ]);
 1842        });
 1843
 1844        // Moving to the beginning of the line should put the first cursor at the beginning of the line,
 1845        // and the second cursor at the first non-whitespace character in the line.
 1846        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1847        assert_eq!(
 1848            editor.selections.display_ranges(cx),
 1849            &[
 1850                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1851                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1852            ]
 1853        );
 1854
 1855        // Moving to the beginning of the line again should be a no-op for the first cursor,
 1856        // and should move the second cursor to the beginning of the line.
 1857        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1858        assert_eq!(
 1859            editor.selections.display_ranges(cx),
 1860            &[
 1861                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1862                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1863            ]
 1864        );
 1865
 1866        // Moving to the beginning of the line again should still be a no-op for the first cursor,
 1867        // and should move the second cursor back to the first non-whitespace character in the line.
 1868        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1869        assert_eq!(
 1870            editor.selections.display_ranges(cx),
 1871            &[
 1872                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1873                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1874            ]
 1875        );
 1876
 1877        // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
 1878        // and to the first non-whitespace character in the line for the second cursor.
 1879        editor.move_to_end_of_line(&move_to_end, window, cx);
 1880        editor.move_left(&MoveLeft, window, cx);
 1881        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1882        assert_eq!(
 1883            editor.selections.display_ranges(cx),
 1884            &[
 1885                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1886                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1887            ]
 1888        );
 1889
 1890        // Selecting to the beginning of the line again should be a no-op for the first cursor,
 1891        // and should select to the beginning of the line for the second cursor.
 1892        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1893        assert_eq!(
 1894            editor.selections.display_ranges(cx),
 1895            &[
 1896                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1897                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1898            ]
 1899        );
 1900
 1901        // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
 1902        // and should delete to the first non-whitespace character in the line for the second cursor.
 1903        editor.move_to_end_of_line(&move_to_end, window, cx);
 1904        editor.move_left(&MoveLeft, window, cx);
 1905        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1906        assert_eq!(editor.text(cx), "c\n  f");
 1907    });
 1908}
 1909
 1910#[gpui::test]
 1911fn test_beginning_of_line_with_cursor_between_line_start_and_indent(cx: &mut TestAppContext) {
 1912    init_test(cx, |_| {});
 1913
 1914    let move_to_beg = MoveToBeginningOfLine {
 1915        stop_at_soft_wraps: true,
 1916        stop_at_indent: true,
 1917    };
 1918
 1919    let editor = cx.add_window(|window, cx| {
 1920        let buffer = MultiBuffer::build_simple("    hello\nworld", cx);
 1921        build_editor(buffer, window, cx)
 1922    });
 1923
 1924    _ = editor.update(cx, |editor, window, cx| {
 1925        // test cursor between line_start and indent_start
 1926        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1927            s.select_display_ranges([
 1928                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3)
 1929            ]);
 1930        });
 1931
 1932        // cursor should move to line_start
 1933        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1934        assert_eq!(
 1935            editor.selections.display_ranges(cx),
 1936            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1937        );
 1938
 1939        // cursor should move to indent_start
 1940        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1941        assert_eq!(
 1942            editor.selections.display_ranges(cx),
 1943            &[DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 4)]
 1944        );
 1945
 1946        // cursor should move to back to line_start
 1947        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1948        assert_eq!(
 1949            editor.selections.display_ranges(cx),
 1950            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1951        );
 1952    });
 1953}
 1954
 1955#[gpui::test]
 1956fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 1957    init_test(cx, |_| {});
 1958
 1959    let editor = cx.add_window(|window, cx| {
 1960        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
 1961        build_editor(buffer, window, cx)
 1962    });
 1963    _ = editor.update(cx, |editor, window, cx| {
 1964        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1965            s.select_display_ranges([
 1966                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
 1967                DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
 1968            ])
 1969        });
 1970        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1971        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 1972
 1973        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1974        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\nˇ  {baz.qux()}", editor, cx);
 1975
 1976        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1977        assert_selection_ranges("use ˇstd::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1978
 1979        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1980        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1981
 1982        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1983        assert_selection_ranges("ˇuse std::str::{foo, ˇbar}\n\n  {baz.qux()}", editor, cx);
 1984
 1985        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1986        assert_selection_ranges("useˇ std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1987
 1988        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1989        assert_selection_ranges("use stdˇ::str::{foo, bar}ˇ\n\n  {baz.qux()}", editor, cx);
 1990
 1991        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1992        assert_selection_ranges("use std::ˇstr::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1993
 1994        editor.move_right(&MoveRight, window, cx);
 1995        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1996        assert_selection_ranges(
 1997            "use std::«ˇs»tr::{foo, bar}\n«ˇ\n»  {baz.qux()}",
 1998            editor,
 1999            cx,
 2000        );
 2001
 2002        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 2003        assert_selection_ranges(
 2004            "use std«ˇ::s»tr::{foo, bar«ˇ}\n\n»  {baz.qux()}",
 2005            editor,
 2006            cx,
 2007        );
 2008
 2009        editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
 2010        assert_selection_ranges(
 2011            "use std::«ˇs»tr::{foo, bar}«ˇ\n\n»  {baz.qux()}",
 2012            editor,
 2013            cx,
 2014        );
 2015    });
 2016}
 2017
 2018#[gpui::test]
 2019fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 2020    init_test(cx, |_| {});
 2021
 2022    let editor = cx.add_window(|window, cx| {
 2023        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 2024        build_editor(buffer, window, cx)
 2025    });
 2026
 2027    _ = editor.update(cx, |editor, window, cx| {
 2028        editor.set_wrap_width(Some(140.0.into()), cx);
 2029        assert_eq!(
 2030            editor.display_text(cx),
 2031            "use one::{\n    two::three::\n    four::five\n};"
 2032        );
 2033
 2034        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2035            s.select_display_ranges([
 2036                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
 2037            ]);
 2038        });
 2039
 2040        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2041        assert_eq!(
 2042            editor.selections.display_ranges(cx),
 2043            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
 2044        );
 2045
 2046        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2047        assert_eq!(
 2048            editor.selections.display_ranges(cx),
 2049            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2050        );
 2051
 2052        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2053        assert_eq!(
 2054            editor.selections.display_ranges(cx),
 2055            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2056        );
 2057
 2058        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2059        assert_eq!(
 2060            editor.selections.display_ranges(cx),
 2061            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
 2062        );
 2063
 2064        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2065        assert_eq!(
 2066            editor.selections.display_ranges(cx),
 2067            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2068        );
 2069
 2070        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2071        assert_eq!(
 2072            editor.selections.display_ranges(cx),
 2073            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2074        );
 2075    });
 2076}
 2077
 2078#[gpui::test]
 2079async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
 2080    init_test(cx, |_| {});
 2081    let mut cx = EditorTestContext::new(cx).await;
 2082
 2083    let line_height = cx.editor(|editor, window, _| {
 2084        editor
 2085            .style()
 2086            .unwrap()
 2087            .text
 2088            .line_height_in_pixels(window.rem_size())
 2089    });
 2090    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2091
 2092    cx.set_state(
 2093        &r#"ˇone
 2094        two
 2095
 2096        three
 2097        fourˇ
 2098        five
 2099
 2100        six"#
 2101            .unindent(),
 2102    );
 2103
 2104    cx.update_editor(|editor, window, cx| {
 2105        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2106    });
 2107    cx.assert_editor_state(
 2108        &r#"one
 2109        two
 2110        ˇ
 2111        three
 2112        four
 2113        five
 2114        ˇ
 2115        six"#
 2116            .unindent(),
 2117    );
 2118
 2119    cx.update_editor(|editor, window, cx| {
 2120        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2121    });
 2122    cx.assert_editor_state(
 2123        &r#"one
 2124        two
 2125
 2126        three
 2127        four
 2128        five
 2129        ˇ
 2130        sixˇ"#
 2131            .unindent(),
 2132    );
 2133
 2134    cx.update_editor(|editor, window, cx| {
 2135        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2136    });
 2137    cx.assert_editor_state(
 2138        &r#"one
 2139        two
 2140
 2141        three
 2142        four
 2143        five
 2144
 2145        sixˇ"#
 2146            .unindent(),
 2147    );
 2148
 2149    cx.update_editor(|editor, window, cx| {
 2150        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2151    });
 2152    cx.assert_editor_state(
 2153        &r#"one
 2154        two
 2155
 2156        three
 2157        four
 2158        five
 2159        ˇ
 2160        six"#
 2161            .unindent(),
 2162    );
 2163
 2164    cx.update_editor(|editor, window, cx| {
 2165        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2166    });
 2167    cx.assert_editor_state(
 2168        &r#"one
 2169        two
 2170        ˇ
 2171        three
 2172        four
 2173        five
 2174
 2175        six"#
 2176            .unindent(),
 2177    );
 2178
 2179    cx.update_editor(|editor, window, cx| {
 2180        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2181    });
 2182    cx.assert_editor_state(
 2183        &r#"ˇone
 2184        two
 2185
 2186        three
 2187        four
 2188        five
 2189
 2190        six"#
 2191            .unindent(),
 2192    );
 2193}
 2194
 2195#[gpui::test]
 2196async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2197    init_test(cx, |_| {});
 2198    let mut cx = EditorTestContext::new(cx).await;
 2199    let line_height = cx.editor(|editor, window, _| {
 2200        editor
 2201            .style()
 2202            .unwrap()
 2203            .text
 2204            .line_height_in_pixels(window.rem_size())
 2205    });
 2206    let window = cx.window;
 2207    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2208
 2209    cx.set_state(
 2210        r#"ˇone
 2211        two
 2212        three
 2213        four
 2214        five
 2215        six
 2216        seven
 2217        eight
 2218        nine
 2219        ten
 2220        "#,
 2221    );
 2222
 2223    cx.update_editor(|editor, window, cx| {
 2224        assert_eq!(
 2225            editor.snapshot(window, cx).scroll_position(),
 2226            gpui::Point::new(0., 0.)
 2227        );
 2228        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2229        assert_eq!(
 2230            editor.snapshot(window, cx).scroll_position(),
 2231            gpui::Point::new(0., 3.)
 2232        );
 2233        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2234        assert_eq!(
 2235            editor.snapshot(window, cx).scroll_position(),
 2236            gpui::Point::new(0., 6.)
 2237        );
 2238        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2239        assert_eq!(
 2240            editor.snapshot(window, cx).scroll_position(),
 2241            gpui::Point::new(0., 3.)
 2242        );
 2243
 2244        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2245        assert_eq!(
 2246            editor.snapshot(window, cx).scroll_position(),
 2247            gpui::Point::new(0., 1.)
 2248        );
 2249        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2250        assert_eq!(
 2251            editor.snapshot(window, cx).scroll_position(),
 2252            gpui::Point::new(0., 3.)
 2253        );
 2254    });
 2255}
 2256
 2257#[gpui::test]
 2258async fn test_autoscroll(cx: &mut TestAppContext) {
 2259    init_test(cx, |_| {});
 2260    let mut cx = EditorTestContext::new(cx).await;
 2261
 2262    let line_height = cx.update_editor(|editor, window, cx| {
 2263        editor.set_vertical_scroll_margin(2, cx);
 2264        editor
 2265            .style()
 2266            .unwrap()
 2267            .text
 2268            .line_height_in_pixels(window.rem_size())
 2269    });
 2270    let window = cx.window;
 2271    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2272
 2273    cx.set_state(
 2274        r#"ˇone
 2275            two
 2276            three
 2277            four
 2278            five
 2279            six
 2280            seven
 2281            eight
 2282            nine
 2283            ten
 2284        "#,
 2285    );
 2286    cx.update_editor(|editor, window, cx| {
 2287        assert_eq!(
 2288            editor.snapshot(window, cx).scroll_position(),
 2289            gpui::Point::new(0., 0.0)
 2290        );
 2291    });
 2292
 2293    // Add a cursor below the visible area. Since both cursors cannot fit
 2294    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2295    // allows the vertical scroll margin below that cursor.
 2296    cx.update_editor(|editor, window, cx| {
 2297        editor.change_selections(Default::default(), window, cx, |selections| {
 2298            selections.select_ranges([
 2299                Point::new(0, 0)..Point::new(0, 0),
 2300                Point::new(6, 0)..Point::new(6, 0),
 2301            ]);
 2302        })
 2303    });
 2304    cx.update_editor(|editor, window, cx| {
 2305        assert_eq!(
 2306            editor.snapshot(window, cx).scroll_position(),
 2307            gpui::Point::new(0., 3.0)
 2308        );
 2309    });
 2310
 2311    // Move down. The editor cursor scrolls down to track the newest cursor.
 2312    cx.update_editor(|editor, window, cx| {
 2313        editor.move_down(&Default::default(), window, cx);
 2314    });
 2315    cx.update_editor(|editor, window, cx| {
 2316        assert_eq!(
 2317            editor.snapshot(window, cx).scroll_position(),
 2318            gpui::Point::new(0., 4.0)
 2319        );
 2320    });
 2321
 2322    // Add a cursor above the visible area. Since both cursors fit on screen,
 2323    // the editor scrolls to show both.
 2324    cx.update_editor(|editor, window, cx| {
 2325        editor.change_selections(Default::default(), window, cx, |selections| {
 2326            selections.select_ranges([
 2327                Point::new(1, 0)..Point::new(1, 0),
 2328                Point::new(6, 0)..Point::new(6, 0),
 2329            ]);
 2330        })
 2331    });
 2332    cx.update_editor(|editor, window, cx| {
 2333        assert_eq!(
 2334            editor.snapshot(window, cx).scroll_position(),
 2335            gpui::Point::new(0., 1.0)
 2336        );
 2337    });
 2338}
 2339
 2340#[gpui::test]
 2341async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2342    init_test(cx, |_| {});
 2343    let mut cx = EditorTestContext::new(cx).await;
 2344
 2345    let line_height = cx.editor(|editor, window, _cx| {
 2346        editor
 2347            .style()
 2348            .unwrap()
 2349            .text
 2350            .line_height_in_pixels(window.rem_size())
 2351    });
 2352    let window = cx.window;
 2353    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2354    cx.set_state(
 2355        &r#"
 2356        ˇone
 2357        two
 2358        threeˇ
 2359        four
 2360        five
 2361        six
 2362        seven
 2363        eight
 2364        nine
 2365        ten
 2366        "#
 2367        .unindent(),
 2368    );
 2369
 2370    cx.update_editor(|editor, window, cx| {
 2371        editor.move_page_down(&MovePageDown::default(), window, cx)
 2372    });
 2373    cx.assert_editor_state(
 2374        &r#"
 2375        one
 2376        two
 2377        three
 2378        ˇfour
 2379        five
 2380        sixˇ
 2381        seven
 2382        eight
 2383        nine
 2384        ten
 2385        "#
 2386        .unindent(),
 2387    );
 2388
 2389    cx.update_editor(|editor, window, cx| {
 2390        editor.move_page_down(&MovePageDown::default(), window, cx)
 2391    });
 2392    cx.assert_editor_state(
 2393        &r#"
 2394        one
 2395        two
 2396        three
 2397        four
 2398        five
 2399        six
 2400        ˇseven
 2401        eight
 2402        nineˇ
 2403        ten
 2404        "#
 2405        .unindent(),
 2406    );
 2407
 2408    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2409    cx.assert_editor_state(
 2410        &r#"
 2411        one
 2412        two
 2413        three
 2414        ˇfour
 2415        five
 2416        sixˇ
 2417        seven
 2418        eight
 2419        nine
 2420        ten
 2421        "#
 2422        .unindent(),
 2423    );
 2424
 2425    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2426    cx.assert_editor_state(
 2427        &r#"
 2428        ˇone
 2429        two
 2430        threeˇ
 2431        four
 2432        five
 2433        six
 2434        seven
 2435        eight
 2436        nine
 2437        ten
 2438        "#
 2439        .unindent(),
 2440    );
 2441
 2442    // Test select collapsing
 2443    cx.update_editor(|editor, window, cx| {
 2444        editor.move_page_down(&MovePageDown::default(), window, cx);
 2445        editor.move_page_down(&MovePageDown::default(), window, cx);
 2446        editor.move_page_down(&MovePageDown::default(), window, cx);
 2447    });
 2448    cx.assert_editor_state(
 2449        &r#"
 2450        one
 2451        two
 2452        three
 2453        four
 2454        five
 2455        six
 2456        seven
 2457        eight
 2458        nine
 2459        ˇten
 2460        ˇ"#
 2461        .unindent(),
 2462    );
 2463}
 2464
 2465#[gpui::test]
 2466async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2467    init_test(cx, |_| {});
 2468    let mut cx = EditorTestContext::new(cx).await;
 2469    cx.set_state("one «two threeˇ» four");
 2470    cx.update_editor(|editor, window, cx| {
 2471        editor.delete_to_beginning_of_line(
 2472            &DeleteToBeginningOfLine {
 2473                stop_at_indent: false,
 2474            },
 2475            window,
 2476            cx,
 2477        );
 2478        assert_eq!(editor.text(cx), " four");
 2479    });
 2480}
 2481
 2482#[gpui::test]
 2483async fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2484    init_test(cx, |_| {});
 2485
 2486    let mut cx = EditorTestContext::new(cx).await;
 2487
 2488    // For an empty selection, the preceding word fragment is deleted.
 2489    // For non-empty selections, only selected characters are deleted.
 2490    cx.set_state("onˇe two t«hreˇ»e four");
 2491    cx.update_editor(|editor, window, cx| {
 2492        editor.delete_to_previous_word_start(
 2493            &DeleteToPreviousWordStart {
 2494                ignore_newlines: false,
 2495                ignore_brackets: false,
 2496            },
 2497            window,
 2498            cx,
 2499        );
 2500    });
 2501    cx.assert_editor_state("ˇe two tˇe four");
 2502
 2503    cx.set_state("e tˇwo te «fˇ»our");
 2504    cx.update_editor(|editor, window, cx| {
 2505        editor.delete_to_next_word_end(
 2506            &DeleteToNextWordEnd {
 2507                ignore_newlines: false,
 2508                ignore_brackets: false,
 2509            },
 2510            window,
 2511            cx,
 2512        );
 2513    });
 2514    cx.assert_editor_state("e tˇ te ˇour");
 2515}
 2516
 2517#[gpui::test]
 2518async fn test_delete_whitespaces(cx: &mut TestAppContext) {
 2519    init_test(cx, |_| {});
 2520
 2521    let mut cx = EditorTestContext::new(cx).await;
 2522
 2523    cx.set_state("here is some text    ˇwith a space");
 2524    cx.update_editor(|editor, window, cx| {
 2525        editor.delete_to_previous_word_start(
 2526            &DeleteToPreviousWordStart {
 2527                ignore_newlines: false,
 2528                ignore_brackets: true,
 2529            },
 2530            window,
 2531            cx,
 2532        );
 2533    });
 2534    // Continuous whitespace sequences are removed entirely, words behind them are not affected by the deletion action.
 2535    cx.assert_editor_state("here is some textˇwith a space");
 2536
 2537    cx.set_state("here is some text    ˇwith a space");
 2538    cx.update_editor(|editor, window, cx| {
 2539        editor.delete_to_previous_word_start(
 2540            &DeleteToPreviousWordStart {
 2541                ignore_newlines: false,
 2542                ignore_brackets: false,
 2543            },
 2544            window,
 2545            cx,
 2546        );
 2547    });
 2548    cx.assert_editor_state("here is some textˇwith a space");
 2549
 2550    cx.set_state("here is some textˇ    with a space");
 2551    cx.update_editor(|editor, window, cx| {
 2552        editor.delete_to_next_word_end(
 2553            &DeleteToNextWordEnd {
 2554                ignore_newlines: false,
 2555                ignore_brackets: true,
 2556            },
 2557            window,
 2558            cx,
 2559        );
 2560    });
 2561    // Same happens in the other direction.
 2562    cx.assert_editor_state("here is some textˇwith a space");
 2563
 2564    cx.set_state("here is some textˇ    with a space");
 2565    cx.update_editor(|editor, window, cx| {
 2566        editor.delete_to_next_word_end(
 2567            &DeleteToNextWordEnd {
 2568                ignore_newlines: false,
 2569                ignore_brackets: false,
 2570            },
 2571            window,
 2572            cx,
 2573        );
 2574    });
 2575    cx.assert_editor_state("here is some textˇwith a space");
 2576
 2577    cx.set_state("here is some textˇ    with a space");
 2578    cx.update_editor(|editor, window, cx| {
 2579        editor.delete_to_next_word_end(
 2580            &DeleteToNextWordEnd {
 2581                ignore_newlines: true,
 2582                ignore_brackets: false,
 2583            },
 2584            window,
 2585            cx,
 2586        );
 2587    });
 2588    cx.assert_editor_state("here is some textˇwith a space");
 2589    cx.update_editor(|editor, window, cx| {
 2590        editor.delete_to_previous_word_start(
 2591            &DeleteToPreviousWordStart {
 2592                ignore_newlines: true,
 2593                ignore_brackets: false,
 2594            },
 2595            window,
 2596            cx,
 2597        );
 2598    });
 2599    cx.assert_editor_state("here is some ˇwith a space");
 2600    cx.update_editor(|editor, window, cx| {
 2601        editor.delete_to_previous_word_start(
 2602            &DeleteToPreviousWordStart {
 2603                ignore_newlines: true,
 2604                ignore_brackets: false,
 2605            },
 2606            window,
 2607            cx,
 2608        );
 2609    });
 2610    // Single whitespaces are removed with the word behind them.
 2611    cx.assert_editor_state("here is ˇwith a space");
 2612    cx.update_editor(|editor, window, cx| {
 2613        editor.delete_to_previous_word_start(
 2614            &DeleteToPreviousWordStart {
 2615                ignore_newlines: true,
 2616                ignore_brackets: false,
 2617            },
 2618            window,
 2619            cx,
 2620        );
 2621    });
 2622    cx.assert_editor_state("here ˇwith a space");
 2623    cx.update_editor(|editor, window, cx| {
 2624        editor.delete_to_previous_word_start(
 2625            &DeleteToPreviousWordStart {
 2626                ignore_newlines: true,
 2627                ignore_brackets: false,
 2628            },
 2629            window,
 2630            cx,
 2631        );
 2632    });
 2633    cx.assert_editor_state("ˇwith a space");
 2634    cx.update_editor(|editor, window, cx| {
 2635        editor.delete_to_previous_word_start(
 2636            &DeleteToPreviousWordStart {
 2637                ignore_newlines: true,
 2638                ignore_brackets: false,
 2639            },
 2640            window,
 2641            cx,
 2642        );
 2643    });
 2644    cx.assert_editor_state("ˇwith a space");
 2645    cx.update_editor(|editor, window, cx| {
 2646        editor.delete_to_next_word_end(
 2647            &DeleteToNextWordEnd {
 2648                ignore_newlines: true,
 2649                ignore_brackets: false,
 2650            },
 2651            window,
 2652            cx,
 2653        );
 2654    });
 2655    // Same happens in the other direction.
 2656    cx.assert_editor_state("ˇ a space");
 2657    cx.update_editor(|editor, window, cx| {
 2658        editor.delete_to_next_word_end(
 2659            &DeleteToNextWordEnd {
 2660                ignore_newlines: true,
 2661                ignore_brackets: false,
 2662            },
 2663            window,
 2664            cx,
 2665        );
 2666    });
 2667    cx.assert_editor_state("ˇ space");
 2668    cx.update_editor(|editor, window, cx| {
 2669        editor.delete_to_next_word_end(
 2670            &DeleteToNextWordEnd {
 2671                ignore_newlines: true,
 2672                ignore_brackets: false,
 2673            },
 2674            window,
 2675            cx,
 2676        );
 2677    });
 2678    cx.assert_editor_state("ˇ");
 2679    cx.update_editor(|editor, window, cx| {
 2680        editor.delete_to_next_word_end(
 2681            &DeleteToNextWordEnd {
 2682                ignore_newlines: true,
 2683                ignore_brackets: false,
 2684            },
 2685            window,
 2686            cx,
 2687        );
 2688    });
 2689    cx.assert_editor_state("ˇ");
 2690    cx.update_editor(|editor, window, cx| {
 2691        editor.delete_to_previous_word_start(
 2692            &DeleteToPreviousWordStart {
 2693                ignore_newlines: true,
 2694                ignore_brackets: false,
 2695            },
 2696            window,
 2697            cx,
 2698        );
 2699    });
 2700    cx.assert_editor_state("ˇ");
 2701}
 2702
 2703#[gpui::test]
 2704async fn test_delete_to_bracket(cx: &mut TestAppContext) {
 2705    init_test(cx, |_| {});
 2706
 2707    let language = Arc::new(
 2708        Language::new(
 2709            LanguageConfig {
 2710                brackets: BracketPairConfig {
 2711                    pairs: vec![
 2712                        BracketPair {
 2713                            start: "\"".to_string(),
 2714                            end: "\"".to_string(),
 2715                            close: true,
 2716                            surround: true,
 2717                            newline: false,
 2718                        },
 2719                        BracketPair {
 2720                            start: "(".to_string(),
 2721                            end: ")".to_string(),
 2722                            close: true,
 2723                            surround: true,
 2724                            newline: true,
 2725                        },
 2726                    ],
 2727                    ..BracketPairConfig::default()
 2728                },
 2729                ..LanguageConfig::default()
 2730            },
 2731            Some(tree_sitter_rust::LANGUAGE.into()),
 2732        )
 2733        .with_brackets_query(
 2734            r#"
 2735                ("(" @open ")" @close)
 2736                ("\"" @open "\"" @close)
 2737            "#,
 2738        )
 2739        .unwrap(),
 2740    );
 2741
 2742    let mut cx = EditorTestContext::new(cx).await;
 2743    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2744
 2745    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 2746    cx.update_editor(|editor, window, cx| {
 2747        editor.delete_to_previous_word_start(
 2748            &DeleteToPreviousWordStart {
 2749                ignore_newlines: true,
 2750                ignore_brackets: false,
 2751            },
 2752            window,
 2753            cx,
 2754        );
 2755    });
 2756    // Deletion stops before brackets if asked to not ignore them.
 2757    cx.assert_editor_state(r#"macro!("ˇCOMMENT");"#);
 2758    cx.update_editor(|editor, window, cx| {
 2759        editor.delete_to_previous_word_start(
 2760            &DeleteToPreviousWordStart {
 2761                ignore_newlines: true,
 2762                ignore_brackets: false,
 2763            },
 2764            window,
 2765            cx,
 2766        );
 2767    });
 2768    // Deletion has to remove a single bracket and then stop again.
 2769    cx.assert_editor_state(r#"macro!(ˇCOMMENT");"#);
 2770
 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    cx.assert_editor_state(r#"macro!ˇCOMMENT");"#);
 2782
 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(r#"ˇCOMMENT");"#);
 2794
 2795    cx.update_editor(|editor, window, cx| {
 2796        editor.delete_to_previous_word_start(
 2797            &DeleteToPreviousWordStart {
 2798                ignore_newlines: true,
 2799                ignore_brackets: false,
 2800            },
 2801            window,
 2802            cx,
 2803        );
 2804    });
 2805    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2806
 2807    cx.update_editor(|editor, window, cx| {
 2808        editor.delete_to_next_word_end(
 2809            &DeleteToNextWordEnd {
 2810                ignore_newlines: true,
 2811                ignore_brackets: false,
 2812            },
 2813            window,
 2814            cx,
 2815        );
 2816    });
 2817    // Brackets on the right are not paired anymore, hence deletion does not stop at them
 2818    cx.assert_editor_state(r#"ˇ");"#);
 2819
 2820    cx.update_editor(|editor, window, cx| {
 2821        editor.delete_to_next_word_end(
 2822            &DeleteToNextWordEnd {
 2823                ignore_newlines: true,
 2824                ignore_brackets: false,
 2825            },
 2826            window,
 2827            cx,
 2828        );
 2829    });
 2830    cx.assert_editor_state(r#"ˇ"#);
 2831
 2832    cx.update_editor(|editor, window, cx| {
 2833        editor.delete_to_next_word_end(
 2834            &DeleteToNextWordEnd {
 2835                ignore_newlines: true,
 2836                ignore_brackets: false,
 2837            },
 2838            window,
 2839            cx,
 2840        );
 2841    });
 2842    cx.assert_editor_state(r#"ˇ"#);
 2843
 2844    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 2845    cx.update_editor(|editor, window, cx| {
 2846        editor.delete_to_previous_word_start(
 2847            &DeleteToPreviousWordStart {
 2848                ignore_newlines: true,
 2849                ignore_brackets: true,
 2850            },
 2851            window,
 2852            cx,
 2853        );
 2854    });
 2855    cx.assert_editor_state(r#"macroˇCOMMENT");"#);
 2856}
 2857
 2858#[gpui::test]
 2859fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 2860    init_test(cx, |_| {});
 2861
 2862    let editor = cx.add_window(|window, cx| {
 2863        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 2864        build_editor(buffer, window, cx)
 2865    });
 2866    let del_to_prev_word_start = DeleteToPreviousWordStart {
 2867        ignore_newlines: false,
 2868        ignore_brackets: false,
 2869    };
 2870    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 2871        ignore_newlines: true,
 2872        ignore_brackets: false,
 2873    };
 2874
 2875    _ = editor.update(cx, |editor, window, cx| {
 2876        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2877            s.select_display_ranges([
 2878                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 2879            ])
 2880        });
 2881        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2882        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 2883        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2884        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
 2885        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2886        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
 2887        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2888        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
 2889        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2890        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 2891        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2892        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2893    });
 2894}
 2895
 2896#[gpui::test]
 2897fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 2898    init_test(cx, |_| {});
 2899
 2900    let editor = cx.add_window(|window, cx| {
 2901        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 2902        build_editor(buffer, window, cx)
 2903    });
 2904    let del_to_next_word_end = DeleteToNextWordEnd {
 2905        ignore_newlines: false,
 2906        ignore_brackets: false,
 2907    };
 2908    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 2909        ignore_newlines: true,
 2910        ignore_brackets: false,
 2911    };
 2912
 2913    _ = editor.update(cx, |editor, window, cx| {
 2914        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2915            s.select_display_ranges([
 2916                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 2917            ])
 2918        });
 2919        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2920        assert_eq!(
 2921            editor.buffer.read(cx).read(cx).text(),
 2922            "one\n   two\nthree\n   four"
 2923        );
 2924        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2925        assert_eq!(
 2926            editor.buffer.read(cx).read(cx).text(),
 2927            "\n   two\nthree\n   four"
 2928        );
 2929        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2930        assert_eq!(
 2931            editor.buffer.read(cx).read(cx).text(),
 2932            "two\nthree\n   four"
 2933        );
 2934        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2935        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 2936        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2937        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   four");
 2938        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2939        assert_eq!(editor.buffer.read(cx).read(cx).text(), "four");
 2940        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2941        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2942    });
 2943}
 2944
 2945#[gpui::test]
 2946fn test_newline(cx: &mut TestAppContext) {
 2947    init_test(cx, |_| {});
 2948
 2949    let editor = cx.add_window(|window, cx| {
 2950        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 2951        build_editor(buffer, window, cx)
 2952    });
 2953
 2954    _ = editor.update(cx, |editor, window, cx| {
 2955        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2956            s.select_display_ranges([
 2957                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2958                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2959                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 2960            ])
 2961        });
 2962
 2963        editor.newline(&Newline, window, cx);
 2964        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 2965    });
 2966}
 2967
 2968#[gpui::test]
 2969fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 2970    init_test(cx, |_| {});
 2971
 2972    let editor = cx.add_window(|window, cx| {
 2973        let buffer = MultiBuffer::build_simple(
 2974            "
 2975                a
 2976                b(
 2977                    X
 2978                )
 2979                c(
 2980                    X
 2981                )
 2982            "
 2983            .unindent()
 2984            .as_str(),
 2985            cx,
 2986        );
 2987        let mut editor = build_editor(buffer, window, cx);
 2988        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2989            s.select_ranges([
 2990                Point::new(2, 4)..Point::new(2, 5),
 2991                Point::new(5, 4)..Point::new(5, 5),
 2992            ])
 2993        });
 2994        editor
 2995    });
 2996
 2997    _ = editor.update(cx, |editor, window, cx| {
 2998        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 2999        editor.buffer.update(cx, |buffer, cx| {
 3000            buffer.edit(
 3001                [
 3002                    (Point::new(1, 2)..Point::new(3, 0), ""),
 3003                    (Point::new(4, 2)..Point::new(6, 0), ""),
 3004                ],
 3005                None,
 3006                cx,
 3007            );
 3008            assert_eq!(
 3009                buffer.read(cx).text(),
 3010                "
 3011                    a
 3012                    b()
 3013                    c()
 3014                "
 3015                .unindent()
 3016            );
 3017        });
 3018        assert_eq!(
 3019            editor.selections.ranges(cx),
 3020            &[
 3021                Point::new(1, 2)..Point::new(1, 2),
 3022                Point::new(2, 2)..Point::new(2, 2),
 3023            ],
 3024        );
 3025
 3026        editor.newline(&Newline, window, cx);
 3027        assert_eq!(
 3028            editor.text(cx),
 3029            "
 3030                a
 3031                b(
 3032                )
 3033                c(
 3034                )
 3035            "
 3036            .unindent()
 3037        );
 3038
 3039        // The selections are moved after the inserted newlines
 3040        assert_eq!(
 3041            editor.selections.ranges(cx),
 3042            &[
 3043                Point::new(2, 0)..Point::new(2, 0),
 3044                Point::new(4, 0)..Point::new(4, 0),
 3045            ],
 3046        );
 3047    });
 3048}
 3049
 3050#[gpui::test]
 3051async fn test_newline_above(cx: &mut TestAppContext) {
 3052    init_test(cx, |settings| {
 3053        settings.defaults.tab_size = NonZeroU32::new(4)
 3054    });
 3055
 3056    let language = Arc::new(
 3057        Language::new(
 3058            LanguageConfig::default(),
 3059            Some(tree_sitter_rust::LANGUAGE.into()),
 3060        )
 3061        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3062        .unwrap(),
 3063    );
 3064
 3065    let mut cx = EditorTestContext::new(cx).await;
 3066    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3067    cx.set_state(indoc! {"
 3068        const a: ˇA = (
 3069 3070                «const_functionˇ»(ˇ),
 3071                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3072 3073        ˇ);ˇ
 3074    "});
 3075
 3076    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 3077    cx.assert_editor_state(indoc! {"
 3078        ˇ
 3079        const a: A = (
 3080            ˇ
 3081            (
 3082                ˇ
 3083                ˇ
 3084                const_function(),
 3085                ˇ
 3086                ˇ
 3087                ˇ
 3088                ˇ
 3089                something_else,
 3090                ˇ
 3091            )
 3092            ˇ
 3093            ˇ
 3094        );
 3095    "});
 3096}
 3097
 3098#[gpui::test]
 3099async fn test_newline_below(cx: &mut TestAppContext) {
 3100    init_test(cx, |settings| {
 3101        settings.defaults.tab_size = NonZeroU32::new(4)
 3102    });
 3103
 3104    let language = Arc::new(
 3105        Language::new(
 3106            LanguageConfig::default(),
 3107            Some(tree_sitter_rust::LANGUAGE.into()),
 3108        )
 3109        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3110        .unwrap(),
 3111    );
 3112
 3113    let mut cx = EditorTestContext::new(cx).await;
 3114    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3115    cx.set_state(indoc! {"
 3116        const a: ˇA = (
 3117 3118                «const_functionˇ»(ˇ),
 3119                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3120 3121        ˇ);ˇ
 3122    "});
 3123
 3124    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 3125    cx.assert_editor_state(indoc! {"
 3126        const a: A = (
 3127            ˇ
 3128            (
 3129                ˇ
 3130                const_function(),
 3131                ˇ
 3132                ˇ
 3133                something_else,
 3134                ˇ
 3135                ˇ
 3136                ˇ
 3137                ˇ
 3138            )
 3139            ˇ
 3140        );
 3141        ˇ
 3142        ˇ
 3143    "});
 3144}
 3145
 3146#[gpui::test]
 3147async fn test_newline_comments(cx: &mut TestAppContext) {
 3148    init_test(cx, |settings| {
 3149        settings.defaults.tab_size = NonZeroU32::new(4)
 3150    });
 3151
 3152    let language = Arc::new(Language::new(
 3153        LanguageConfig {
 3154            line_comments: vec!["// ".into()],
 3155            ..LanguageConfig::default()
 3156        },
 3157        None,
 3158    ));
 3159    {
 3160        let mut cx = EditorTestContext::new(cx).await;
 3161        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3162        cx.set_state(indoc! {"
 3163        // Fooˇ
 3164    "});
 3165
 3166        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3167        cx.assert_editor_state(indoc! {"
 3168        // Foo
 3169        // ˇ
 3170    "});
 3171        // Ensure that we add comment prefix when existing line contains space
 3172        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3173        cx.assert_editor_state(
 3174            indoc! {"
 3175        // Foo
 3176        //s
 3177        // ˇ
 3178    "}
 3179            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3180            .as_str(),
 3181        );
 3182        // Ensure that we add comment prefix when existing line does not contain space
 3183        cx.set_state(indoc! {"
 3184        // Foo
 3185        //ˇ
 3186    "});
 3187        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3188        cx.assert_editor_state(indoc! {"
 3189        // Foo
 3190        //
 3191        // ˇ
 3192    "});
 3193        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 3194        cx.set_state(indoc! {"
 3195        ˇ// Foo
 3196    "});
 3197        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3198        cx.assert_editor_state(indoc! {"
 3199
 3200        ˇ// Foo
 3201    "});
 3202    }
 3203    // Ensure that comment continuations can be disabled.
 3204    update_test_language_settings(cx, |settings| {
 3205        settings.defaults.extend_comment_on_newline = Some(false);
 3206    });
 3207    let mut cx = EditorTestContext::new(cx).await;
 3208    cx.set_state(indoc! {"
 3209        // Fooˇ
 3210    "});
 3211    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3212    cx.assert_editor_state(indoc! {"
 3213        // Foo
 3214        ˇ
 3215    "});
 3216}
 3217
 3218#[gpui::test]
 3219async fn test_newline_comments_with_multiple_delimiters(cx: &mut TestAppContext) {
 3220    init_test(cx, |settings| {
 3221        settings.defaults.tab_size = NonZeroU32::new(4)
 3222    });
 3223
 3224    let language = Arc::new(Language::new(
 3225        LanguageConfig {
 3226            line_comments: vec!["// ".into(), "/// ".into()],
 3227            ..LanguageConfig::default()
 3228        },
 3229        None,
 3230    ));
 3231    {
 3232        let mut cx = EditorTestContext::new(cx).await;
 3233        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3234        cx.set_state(indoc! {"
 3235        //ˇ
 3236    "});
 3237        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3238        cx.assert_editor_state(indoc! {"
 3239        //
 3240        // ˇ
 3241    "});
 3242
 3243        cx.set_state(indoc! {"
 3244        ///ˇ
 3245    "});
 3246        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3247        cx.assert_editor_state(indoc! {"
 3248        ///
 3249        /// ˇ
 3250    "});
 3251    }
 3252}
 3253
 3254#[gpui::test]
 3255async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 3256    init_test(cx, |settings| {
 3257        settings.defaults.tab_size = NonZeroU32::new(4)
 3258    });
 3259
 3260    let language = Arc::new(
 3261        Language::new(
 3262            LanguageConfig {
 3263                documentation_comment: Some(language::BlockCommentConfig {
 3264                    start: "/**".into(),
 3265                    end: "*/".into(),
 3266                    prefix: "* ".into(),
 3267                    tab_size: 1,
 3268                }),
 3269
 3270                ..LanguageConfig::default()
 3271            },
 3272            Some(tree_sitter_rust::LANGUAGE.into()),
 3273        )
 3274        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 3275        .unwrap(),
 3276    );
 3277
 3278    {
 3279        let mut cx = EditorTestContext::new(cx).await;
 3280        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3281        cx.set_state(indoc! {"
 3282        /**ˇ
 3283    "});
 3284
 3285        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3286        cx.assert_editor_state(indoc! {"
 3287        /**
 3288         * ˇ
 3289    "});
 3290        // Ensure that if cursor is before the comment start,
 3291        // we do not actually insert a comment prefix.
 3292        cx.set_state(indoc! {"
 3293        ˇ/**
 3294    "});
 3295        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3296        cx.assert_editor_state(indoc! {"
 3297
 3298        ˇ/**
 3299    "});
 3300        // Ensure that if cursor is between it doesn't add comment prefix.
 3301        cx.set_state(indoc! {"
 3302        /*ˇ*
 3303    "});
 3304        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3305        cx.assert_editor_state(indoc! {"
 3306        /*
 3307        ˇ*
 3308    "});
 3309        // Ensure that if suffix exists on same line after cursor it adds new line.
 3310        cx.set_state(indoc! {"
 3311        /**ˇ*/
 3312    "});
 3313        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3314        cx.assert_editor_state(indoc! {"
 3315        /**
 3316         * ˇ
 3317         */
 3318    "});
 3319        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3320        cx.set_state(indoc! {"
 3321        /**ˇ */
 3322    "});
 3323        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3324        cx.assert_editor_state(indoc! {"
 3325        /**
 3326         * ˇ
 3327         */
 3328    "});
 3329        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3330        cx.set_state(indoc! {"
 3331        /** ˇ*/
 3332    "});
 3333        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3334        cx.assert_editor_state(
 3335            indoc! {"
 3336        /**s
 3337         * ˇ
 3338         */
 3339    "}
 3340            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3341            .as_str(),
 3342        );
 3343        // Ensure that delimiter space is preserved when newline on already
 3344        // spaced delimiter.
 3345        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3346        cx.assert_editor_state(
 3347            indoc! {"
 3348        /**s
 3349         *s
 3350         * ˇ
 3351         */
 3352    "}
 3353            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3354            .as_str(),
 3355        );
 3356        // Ensure that delimiter space is preserved when space is not
 3357        // on existing delimiter.
 3358        cx.set_state(indoc! {"
 3359        /**
 3360 3361         */
 3362    "});
 3363        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3364        cx.assert_editor_state(indoc! {"
 3365        /**
 3366         *
 3367         * ˇ
 3368         */
 3369    "});
 3370        // Ensure that if suffix exists on same line after cursor it
 3371        // doesn't add extra new line if prefix is not on same line.
 3372        cx.set_state(indoc! {"
 3373        /**
 3374        ˇ*/
 3375    "});
 3376        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3377        cx.assert_editor_state(indoc! {"
 3378        /**
 3379
 3380        ˇ*/
 3381    "});
 3382        // Ensure that it detects suffix after existing prefix.
 3383        cx.set_state(indoc! {"
 3384        /**ˇ/
 3385    "});
 3386        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3387        cx.assert_editor_state(indoc! {"
 3388        /**
 3389        ˇ/
 3390    "});
 3391        // Ensure that if suffix exists on same line before
 3392        // cursor it does not add comment prefix.
 3393        cx.set_state(indoc! {"
 3394        /** */ˇ
 3395    "});
 3396        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3397        cx.assert_editor_state(indoc! {"
 3398        /** */
 3399        ˇ
 3400    "});
 3401        // Ensure that if suffix exists on same line before
 3402        // cursor it does not add comment prefix.
 3403        cx.set_state(indoc! {"
 3404        /**
 3405         *
 3406         */ˇ
 3407    "});
 3408        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3409        cx.assert_editor_state(indoc! {"
 3410        /**
 3411         *
 3412         */
 3413         ˇ
 3414    "});
 3415
 3416        // Ensure that inline comment followed by code
 3417        // doesn't add comment prefix on newline
 3418        cx.set_state(indoc! {"
 3419        /** */ textˇ
 3420    "});
 3421        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3422        cx.assert_editor_state(indoc! {"
 3423        /** */ text
 3424        ˇ
 3425    "});
 3426
 3427        // Ensure that text after comment end tag
 3428        // doesn't add comment prefix on newline
 3429        cx.set_state(indoc! {"
 3430        /**
 3431         *
 3432         */ˇtext
 3433    "});
 3434        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3435        cx.assert_editor_state(indoc! {"
 3436        /**
 3437         *
 3438         */
 3439         ˇtext
 3440    "});
 3441
 3442        // Ensure if not comment block it doesn't
 3443        // add comment prefix on newline
 3444        cx.set_state(indoc! {"
 3445        * textˇ
 3446    "});
 3447        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3448        cx.assert_editor_state(indoc! {"
 3449        * text
 3450        ˇ
 3451    "});
 3452    }
 3453    // Ensure that comment continuations can be disabled.
 3454    update_test_language_settings(cx, |settings| {
 3455        settings.defaults.extend_comment_on_newline = Some(false);
 3456    });
 3457    let mut cx = EditorTestContext::new(cx).await;
 3458    cx.set_state(indoc! {"
 3459        /**ˇ
 3460    "});
 3461    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3462    cx.assert_editor_state(indoc! {"
 3463        /**
 3464        ˇ
 3465    "});
 3466}
 3467
 3468#[gpui::test]
 3469async fn test_newline_comments_with_block_comment(cx: &mut TestAppContext) {
 3470    init_test(cx, |settings| {
 3471        settings.defaults.tab_size = NonZeroU32::new(4)
 3472    });
 3473
 3474    let lua_language = Arc::new(Language::new(
 3475        LanguageConfig {
 3476            line_comments: vec!["--".into()],
 3477            block_comment: Some(language::BlockCommentConfig {
 3478                start: "--[[".into(),
 3479                prefix: "".into(),
 3480                end: "]]".into(),
 3481                tab_size: 0,
 3482            }),
 3483            ..LanguageConfig::default()
 3484        },
 3485        None,
 3486    ));
 3487
 3488    let mut cx = EditorTestContext::new(cx).await;
 3489    cx.update_buffer(|buffer, cx| buffer.set_language(Some(lua_language), cx));
 3490
 3491    // Line with line comment should extend
 3492    cx.set_state(indoc! {"
 3493        --ˇ
 3494    "});
 3495    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3496    cx.assert_editor_state(indoc! {"
 3497        --
 3498        --ˇ
 3499    "});
 3500
 3501    // Line with block comment that matches line comment should not extend
 3502    cx.set_state(indoc! {"
 3503        --[[ˇ
 3504    "});
 3505    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3506    cx.assert_editor_state(indoc! {"
 3507        --[[
 3508        ˇ
 3509    "});
 3510}
 3511
 3512#[gpui::test]
 3513fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 3514    init_test(cx, |_| {});
 3515
 3516    let editor = cx.add_window(|window, cx| {
 3517        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 3518        let mut editor = build_editor(buffer, window, cx);
 3519        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3520            s.select_ranges([3..4, 11..12, 19..20])
 3521        });
 3522        editor
 3523    });
 3524
 3525    _ = editor.update(cx, |editor, window, cx| {
 3526        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3527        editor.buffer.update(cx, |buffer, cx| {
 3528            buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
 3529            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 3530        });
 3531        assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
 3532
 3533        editor.insert("Z", window, cx);
 3534        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 3535
 3536        // The selections are moved after the inserted characters
 3537        assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
 3538    });
 3539}
 3540
 3541#[gpui::test]
 3542async fn test_tab(cx: &mut TestAppContext) {
 3543    init_test(cx, |settings| {
 3544        settings.defaults.tab_size = NonZeroU32::new(3)
 3545    });
 3546
 3547    let mut cx = EditorTestContext::new(cx).await;
 3548    cx.set_state(indoc! {"
 3549        ˇabˇc
 3550        ˇ🏀ˇ🏀ˇefg
 3551 3552    "});
 3553    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3554    cx.assert_editor_state(indoc! {"
 3555           ˇab ˇc
 3556           ˇ🏀  ˇ🏀  ˇefg
 3557        d  ˇ
 3558    "});
 3559
 3560    cx.set_state(indoc! {"
 3561        a
 3562        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3563    "});
 3564    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3565    cx.assert_editor_state(indoc! {"
 3566        a
 3567           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3568    "});
 3569}
 3570
 3571#[gpui::test]
 3572async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3573    init_test(cx, |_| {});
 3574
 3575    let mut cx = EditorTestContext::new(cx).await;
 3576    let language = Arc::new(
 3577        Language::new(
 3578            LanguageConfig::default(),
 3579            Some(tree_sitter_rust::LANGUAGE.into()),
 3580        )
 3581        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3582        .unwrap(),
 3583    );
 3584    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3585
 3586    // test when all cursors are not at suggested indent
 3587    // then simply move to their suggested indent location
 3588    cx.set_state(indoc! {"
 3589        const a: B = (
 3590            c(
 3591        ˇ
 3592        ˇ    )
 3593        );
 3594    "});
 3595    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3596    cx.assert_editor_state(indoc! {"
 3597        const a: B = (
 3598            c(
 3599                ˇ
 3600            ˇ)
 3601        );
 3602    "});
 3603
 3604    // test cursor already at suggested indent not moving when
 3605    // other cursors are yet to reach their suggested indents
 3606    cx.set_state(indoc! {"
 3607        ˇ
 3608        const a: B = (
 3609            c(
 3610                d(
 3611        ˇ
 3612                )
 3613        ˇ
 3614        ˇ    )
 3615        );
 3616    "});
 3617    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3618    cx.assert_editor_state(indoc! {"
 3619        ˇ
 3620        const a: B = (
 3621            c(
 3622                d(
 3623                    ˇ
 3624                )
 3625                ˇ
 3626            ˇ)
 3627        );
 3628    "});
 3629    // test when all cursors are at suggested indent then tab is inserted
 3630    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3631    cx.assert_editor_state(indoc! {"
 3632            ˇ
 3633        const a: B = (
 3634            c(
 3635                d(
 3636                        ˇ
 3637                )
 3638                    ˇ
 3639                ˇ)
 3640        );
 3641    "});
 3642
 3643    // test when current indent is less than suggested indent,
 3644    // we adjust line to match suggested indent and move cursor to it
 3645    //
 3646    // when no other cursor is at word boundary, all of them should move
 3647    cx.set_state(indoc! {"
 3648        const a: B = (
 3649            c(
 3650                d(
 3651        ˇ
 3652        ˇ   )
 3653        ˇ   )
 3654        );
 3655    "});
 3656    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3657    cx.assert_editor_state(indoc! {"
 3658        const a: B = (
 3659            c(
 3660                d(
 3661                    ˇ
 3662                ˇ)
 3663            ˇ)
 3664        );
 3665    "});
 3666
 3667    // test when current indent is less than suggested indent,
 3668    // we adjust line to match suggested indent and move cursor to it
 3669    //
 3670    // when some other cursor is at word boundary, it should not move
 3671    cx.set_state(indoc! {"
 3672        const a: B = (
 3673            c(
 3674                d(
 3675        ˇ
 3676        ˇ   )
 3677           ˇ)
 3678        );
 3679    "});
 3680    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3681    cx.assert_editor_state(indoc! {"
 3682        const a: B = (
 3683            c(
 3684                d(
 3685                    ˇ
 3686                ˇ)
 3687            ˇ)
 3688        );
 3689    "});
 3690
 3691    // test when current indent is more than suggested indent,
 3692    // we just move cursor to current indent instead of suggested indent
 3693    //
 3694    // when no other cursor is at word boundary, all of them should move
 3695    cx.set_state(indoc! {"
 3696        const a: B = (
 3697            c(
 3698                d(
 3699        ˇ
 3700        ˇ                )
 3701        ˇ   )
 3702        );
 3703    "});
 3704    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3705    cx.assert_editor_state(indoc! {"
 3706        const a: B = (
 3707            c(
 3708                d(
 3709                    ˇ
 3710                        ˇ)
 3711            ˇ)
 3712        );
 3713    "});
 3714    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3715    cx.assert_editor_state(indoc! {"
 3716        const a: B = (
 3717            c(
 3718                d(
 3719                        ˇ
 3720                            ˇ)
 3721                ˇ)
 3722        );
 3723    "});
 3724
 3725    // test when current indent is more than suggested indent,
 3726    // we just move cursor to current indent instead of suggested indent
 3727    //
 3728    // when some other cursor is at word boundary, it doesn't move
 3729    cx.set_state(indoc! {"
 3730        const a: B = (
 3731            c(
 3732                d(
 3733        ˇ
 3734        ˇ                )
 3735            ˇ)
 3736        );
 3737    "});
 3738    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3739    cx.assert_editor_state(indoc! {"
 3740        const a: B = (
 3741            c(
 3742                d(
 3743                    ˇ
 3744                        ˇ)
 3745            ˇ)
 3746        );
 3747    "});
 3748
 3749    // handle auto-indent when there are multiple cursors on the same line
 3750    cx.set_state(indoc! {"
 3751        const a: B = (
 3752            c(
 3753        ˇ    ˇ
 3754        ˇ    )
 3755        );
 3756    "});
 3757    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3758    cx.assert_editor_state(indoc! {"
 3759        const a: B = (
 3760            c(
 3761                ˇ
 3762            ˇ)
 3763        );
 3764    "});
 3765}
 3766
 3767#[gpui::test]
 3768async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 3769    init_test(cx, |settings| {
 3770        settings.defaults.tab_size = NonZeroU32::new(3)
 3771    });
 3772
 3773    let mut cx = EditorTestContext::new(cx).await;
 3774    cx.set_state(indoc! {"
 3775         ˇ
 3776        \t ˇ
 3777        \t  ˇ
 3778        \t   ˇ
 3779         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 3780    "});
 3781
 3782    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3783    cx.assert_editor_state(indoc! {"
 3784           ˇ
 3785        \t   ˇ
 3786        \t   ˇ
 3787        \t      ˇ
 3788         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 3789    "});
 3790}
 3791
 3792#[gpui::test]
 3793async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 3794    init_test(cx, |settings| {
 3795        settings.defaults.tab_size = NonZeroU32::new(4)
 3796    });
 3797
 3798    let language = Arc::new(
 3799        Language::new(
 3800            LanguageConfig::default(),
 3801            Some(tree_sitter_rust::LANGUAGE.into()),
 3802        )
 3803        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 3804        .unwrap(),
 3805    );
 3806
 3807    let mut cx = EditorTestContext::new(cx).await;
 3808    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3809    cx.set_state(indoc! {"
 3810        fn a() {
 3811            if b {
 3812        \t ˇc
 3813            }
 3814        }
 3815    "});
 3816
 3817    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3818    cx.assert_editor_state(indoc! {"
 3819        fn a() {
 3820            if b {
 3821                ˇc
 3822            }
 3823        }
 3824    "});
 3825}
 3826
 3827#[gpui::test]
 3828async fn test_indent_outdent(cx: &mut TestAppContext) {
 3829    init_test(cx, |settings| {
 3830        settings.defaults.tab_size = NonZeroU32::new(4);
 3831    });
 3832
 3833    let mut cx = EditorTestContext::new(cx).await;
 3834
 3835    cx.set_state(indoc! {"
 3836          «oneˇ» «twoˇ»
 3837        three
 3838         four
 3839    "});
 3840    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3841    cx.assert_editor_state(indoc! {"
 3842            «oneˇ» «twoˇ»
 3843        three
 3844         four
 3845    "});
 3846
 3847    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3848    cx.assert_editor_state(indoc! {"
 3849        «oneˇ» «twoˇ»
 3850        three
 3851         four
 3852    "});
 3853
 3854    // select across line ending
 3855    cx.set_state(indoc! {"
 3856        one two
 3857        t«hree
 3858        ˇ» four
 3859    "});
 3860    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3861    cx.assert_editor_state(indoc! {"
 3862        one two
 3863            t«hree
 3864        ˇ» four
 3865    "});
 3866
 3867    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3868    cx.assert_editor_state(indoc! {"
 3869        one two
 3870        t«hree
 3871        ˇ» four
 3872    "});
 3873
 3874    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3875    cx.set_state(indoc! {"
 3876        one two
 3877        ˇthree
 3878            four
 3879    "});
 3880    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3881    cx.assert_editor_state(indoc! {"
 3882        one two
 3883            ˇthree
 3884            four
 3885    "});
 3886
 3887    cx.set_state(indoc! {"
 3888        one two
 3889        ˇ    three
 3890            four
 3891    "});
 3892    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3893    cx.assert_editor_state(indoc! {"
 3894        one two
 3895        ˇthree
 3896            four
 3897    "});
 3898}
 3899
 3900#[gpui::test]
 3901async fn test_indent_yaml_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 3902    // This is a regression test for issue #33761
 3903    init_test(cx, |_| {});
 3904
 3905    let mut cx = EditorTestContext::new(cx).await;
 3906    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3907    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3908
 3909    cx.set_state(
 3910        r#"ˇ#     ingress:
 3911ˇ#         api:
 3912ˇ#             enabled: false
 3913ˇ#             pathType: Prefix
 3914ˇ#           console:
 3915ˇ#               enabled: false
 3916ˇ#               pathType: Prefix
 3917"#,
 3918    );
 3919
 3920    // Press tab to indent all lines
 3921    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3922
 3923    cx.assert_editor_state(
 3924        r#"    ˇ#     ingress:
 3925    ˇ#         api:
 3926    ˇ#             enabled: false
 3927    ˇ#             pathType: Prefix
 3928    ˇ#           console:
 3929    ˇ#               enabled: false
 3930    ˇ#               pathType: Prefix
 3931"#,
 3932    );
 3933}
 3934
 3935#[gpui::test]
 3936async fn test_indent_yaml_non_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 3937    // This is a test to make sure our fix for issue #33761 didn't break anything
 3938    init_test(cx, |_| {});
 3939
 3940    let mut cx = EditorTestContext::new(cx).await;
 3941    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3942    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3943
 3944    cx.set_state(
 3945        r#"ˇingress:
 3946ˇ  api:
 3947ˇ    enabled: false
 3948ˇ    pathType: Prefix
 3949"#,
 3950    );
 3951
 3952    // Press tab to indent all lines
 3953    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3954
 3955    cx.assert_editor_state(
 3956        r#"ˇingress:
 3957    ˇapi:
 3958        ˇenabled: false
 3959        ˇpathType: Prefix
 3960"#,
 3961    );
 3962}
 3963
 3964#[gpui::test]
 3965async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 3966    init_test(cx, |settings| {
 3967        settings.defaults.hard_tabs = Some(true);
 3968    });
 3969
 3970    let mut cx = EditorTestContext::new(cx).await;
 3971
 3972    // select two ranges on one line
 3973    cx.set_state(indoc! {"
 3974        «oneˇ» «twoˇ»
 3975        three
 3976        four
 3977    "});
 3978    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3979    cx.assert_editor_state(indoc! {"
 3980        \t«oneˇ» «twoˇ»
 3981        three
 3982        four
 3983    "});
 3984    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3985    cx.assert_editor_state(indoc! {"
 3986        \t\t«oneˇ» «twoˇ»
 3987        three
 3988        four
 3989    "});
 3990    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3991    cx.assert_editor_state(indoc! {"
 3992        \t«oneˇ» «twoˇ»
 3993        three
 3994        four
 3995    "});
 3996    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3997    cx.assert_editor_state(indoc! {"
 3998        «oneˇ» «twoˇ»
 3999        three
 4000        four
 4001    "});
 4002
 4003    // select across a line ending
 4004    cx.set_state(indoc! {"
 4005        one two
 4006        t«hree
 4007        ˇ»four
 4008    "});
 4009    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4010    cx.assert_editor_state(indoc! {"
 4011        one two
 4012        \tt«hree
 4013        ˇ»four
 4014    "});
 4015    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4016    cx.assert_editor_state(indoc! {"
 4017        one two
 4018        \t\tt«hree
 4019        ˇ»four
 4020    "});
 4021    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4022    cx.assert_editor_state(indoc! {"
 4023        one two
 4024        \tt«hree
 4025        ˇ»four
 4026    "});
 4027    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4028    cx.assert_editor_state(indoc! {"
 4029        one two
 4030        t«hree
 4031        ˇ»four
 4032    "});
 4033
 4034    // Ensure that indenting/outdenting works when the cursor is at column 0.
 4035    cx.set_state(indoc! {"
 4036        one two
 4037        ˇthree
 4038        four
 4039    "});
 4040    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4041    cx.assert_editor_state(indoc! {"
 4042        one two
 4043        ˇthree
 4044        four
 4045    "});
 4046    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4047    cx.assert_editor_state(indoc! {"
 4048        one two
 4049        \tˇthree
 4050        four
 4051    "});
 4052    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4053    cx.assert_editor_state(indoc! {"
 4054        one two
 4055        ˇthree
 4056        four
 4057    "});
 4058}
 4059
 4060#[gpui::test]
 4061fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 4062    init_test(cx, |settings| {
 4063        settings.languages.0.extend([
 4064            (
 4065                "TOML".into(),
 4066                LanguageSettingsContent {
 4067                    tab_size: NonZeroU32::new(2),
 4068                    ..Default::default()
 4069                },
 4070            ),
 4071            (
 4072                "Rust".into(),
 4073                LanguageSettingsContent {
 4074                    tab_size: NonZeroU32::new(4),
 4075                    ..Default::default()
 4076                },
 4077            ),
 4078        ]);
 4079    });
 4080
 4081    let toml_language = Arc::new(Language::new(
 4082        LanguageConfig {
 4083            name: "TOML".into(),
 4084            ..Default::default()
 4085        },
 4086        None,
 4087    ));
 4088    let rust_language = Arc::new(Language::new(
 4089        LanguageConfig {
 4090            name: "Rust".into(),
 4091            ..Default::default()
 4092        },
 4093        None,
 4094    ));
 4095
 4096    let toml_buffer =
 4097        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 4098    let rust_buffer =
 4099        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 4100    let multibuffer = cx.new(|cx| {
 4101        let mut multibuffer = MultiBuffer::new(ReadWrite);
 4102        multibuffer.push_excerpts(
 4103            toml_buffer.clone(),
 4104            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 4105            cx,
 4106        );
 4107        multibuffer.push_excerpts(
 4108            rust_buffer.clone(),
 4109            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 4110            cx,
 4111        );
 4112        multibuffer
 4113    });
 4114
 4115    cx.add_window(|window, cx| {
 4116        let mut editor = build_editor(multibuffer, window, cx);
 4117
 4118        assert_eq!(
 4119            editor.text(cx),
 4120            indoc! {"
 4121                a = 1
 4122                b = 2
 4123
 4124                const c: usize = 3;
 4125            "}
 4126        );
 4127
 4128        select_ranges(
 4129            &mut editor,
 4130            indoc! {"
 4131                «aˇ» = 1
 4132                b = 2
 4133
 4134                «const c:ˇ» usize = 3;
 4135            "},
 4136            window,
 4137            cx,
 4138        );
 4139
 4140        editor.tab(&Tab, window, cx);
 4141        assert_text_with_selections(
 4142            &mut editor,
 4143            indoc! {"
 4144                  «aˇ» = 1
 4145                b = 2
 4146
 4147                    «const c:ˇ» usize = 3;
 4148            "},
 4149            cx,
 4150        );
 4151        editor.backtab(&Backtab, window, cx);
 4152        assert_text_with_selections(
 4153            &mut editor,
 4154            indoc! {"
 4155                «aˇ» = 1
 4156                b = 2
 4157
 4158                «const c:ˇ» usize = 3;
 4159            "},
 4160            cx,
 4161        );
 4162
 4163        editor
 4164    });
 4165}
 4166
 4167#[gpui::test]
 4168async fn test_backspace(cx: &mut TestAppContext) {
 4169    init_test(cx, |_| {});
 4170
 4171    let mut cx = EditorTestContext::new(cx).await;
 4172
 4173    // Basic backspace
 4174    cx.set_state(indoc! {"
 4175        onˇe two three
 4176        fou«rˇ» five six
 4177        seven «ˇeight nine
 4178        »ten
 4179    "});
 4180    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4181    cx.assert_editor_state(indoc! {"
 4182        oˇe two three
 4183        fouˇ five six
 4184        seven ˇten
 4185    "});
 4186
 4187    // Test backspace inside and around indents
 4188    cx.set_state(indoc! {"
 4189        zero
 4190            ˇone
 4191                ˇtwo
 4192            ˇ ˇ ˇ  three
 4193        ˇ  ˇ  four
 4194    "});
 4195    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4196    cx.assert_editor_state(indoc! {"
 4197        zero
 4198        ˇone
 4199            ˇtwo
 4200        ˇ  threeˇ  four
 4201    "});
 4202}
 4203
 4204#[gpui::test]
 4205async fn test_delete(cx: &mut TestAppContext) {
 4206    init_test(cx, |_| {});
 4207
 4208    let mut cx = EditorTestContext::new(cx).await;
 4209    cx.set_state(indoc! {"
 4210        onˇe two three
 4211        fou«rˇ» five six
 4212        seven «ˇeight nine
 4213        »ten
 4214    "});
 4215    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 4216    cx.assert_editor_state(indoc! {"
 4217        onˇ two three
 4218        fouˇ five six
 4219        seven ˇten
 4220    "});
 4221}
 4222
 4223#[gpui::test]
 4224fn test_delete_line(cx: &mut TestAppContext) {
 4225    init_test(cx, |_| {});
 4226
 4227    let editor = cx.add_window(|window, cx| {
 4228        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4229        build_editor(buffer, window, cx)
 4230    });
 4231    _ = editor.update(cx, |editor, window, cx| {
 4232        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4233            s.select_display_ranges([
 4234                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4235                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4236                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4237            ])
 4238        });
 4239        editor.delete_line(&DeleteLine, window, cx);
 4240        assert_eq!(editor.display_text(cx), "ghi");
 4241        assert_eq!(
 4242            editor.selections.display_ranges(cx),
 4243            vec![
 4244                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 4245                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
 4246            ]
 4247        );
 4248    });
 4249
 4250    let editor = cx.add_window(|window, cx| {
 4251        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4252        build_editor(buffer, window, cx)
 4253    });
 4254    _ = editor.update(cx, |editor, window, cx| {
 4255        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4256            s.select_display_ranges([
 4257                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 4258            ])
 4259        });
 4260        editor.delete_line(&DeleteLine, window, cx);
 4261        assert_eq!(editor.display_text(cx), "ghi\n");
 4262        assert_eq!(
 4263            editor.selections.display_ranges(cx),
 4264            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 4265        );
 4266    });
 4267}
 4268
 4269#[gpui::test]
 4270fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 4271    init_test(cx, |_| {});
 4272
 4273    cx.add_window(|window, cx| {
 4274        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4275        let mut editor = build_editor(buffer.clone(), window, cx);
 4276        let buffer = buffer.read(cx).as_singleton().unwrap();
 4277
 4278        assert_eq!(
 4279            editor.selections.ranges::<Point>(cx),
 4280            &[Point::new(0, 0)..Point::new(0, 0)]
 4281        );
 4282
 4283        // When on single line, replace newline at end by space
 4284        editor.join_lines(&JoinLines, window, cx);
 4285        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4286        assert_eq!(
 4287            editor.selections.ranges::<Point>(cx),
 4288            &[Point::new(0, 3)..Point::new(0, 3)]
 4289        );
 4290
 4291        // When multiple lines are selected, remove newlines that are spanned by the selection
 4292        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4293            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 4294        });
 4295        editor.join_lines(&JoinLines, window, cx);
 4296        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 4297        assert_eq!(
 4298            editor.selections.ranges::<Point>(cx),
 4299            &[Point::new(0, 11)..Point::new(0, 11)]
 4300        );
 4301
 4302        // Undo should be transactional
 4303        editor.undo(&Undo, window, cx);
 4304        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4305        assert_eq!(
 4306            editor.selections.ranges::<Point>(cx),
 4307            &[Point::new(0, 5)..Point::new(2, 2)]
 4308        );
 4309
 4310        // When joining an empty line don't insert a space
 4311        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4312            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 4313        });
 4314        editor.join_lines(&JoinLines, window, cx);
 4315        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 4316        assert_eq!(
 4317            editor.selections.ranges::<Point>(cx),
 4318            [Point::new(2, 3)..Point::new(2, 3)]
 4319        );
 4320
 4321        // We can remove trailing newlines
 4322        editor.join_lines(&JoinLines, window, cx);
 4323        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4324        assert_eq!(
 4325            editor.selections.ranges::<Point>(cx),
 4326            [Point::new(2, 3)..Point::new(2, 3)]
 4327        );
 4328
 4329        // We don't blow up on the last line
 4330        editor.join_lines(&JoinLines, window, cx);
 4331        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4332        assert_eq!(
 4333            editor.selections.ranges::<Point>(cx),
 4334            [Point::new(2, 3)..Point::new(2, 3)]
 4335        );
 4336
 4337        // reset to test indentation
 4338        editor.buffer.update(cx, |buffer, cx| {
 4339            buffer.edit(
 4340                [
 4341                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 4342                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 4343                ],
 4344                None,
 4345                cx,
 4346            )
 4347        });
 4348
 4349        // We remove any leading spaces
 4350        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 4351        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4352            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 4353        });
 4354        editor.join_lines(&JoinLines, window, cx);
 4355        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 4356
 4357        // We don't insert a space for a line containing only spaces
 4358        editor.join_lines(&JoinLines, window, cx);
 4359        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 4360
 4361        // We ignore any leading tabs
 4362        editor.join_lines(&JoinLines, window, cx);
 4363        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 4364
 4365        editor
 4366    });
 4367}
 4368
 4369#[gpui::test]
 4370fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 4371    init_test(cx, |_| {});
 4372
 4373    cx.add_window(|window, cx| {
 4374        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4375        let mut editor = build_editor(buffer.clone(), window, cx);
 4376        let buffer = buffer.read(cx).as_singleton().unwrap();
 4377
 4378        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4379            s.select_ranges([
 4380                Point::new(0, 2)..Point::new(1, 1),
 4381                Point::new(1, 2)..Point::new(1, 2),
 4382                Point::new(3, 1)..Point::new(3, 2),
 4383            ])
 4384        });
 4385
 4386        editor.join_lines(&JoinLines, window, cx);
 4387        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 4388
 4389        assert_eq!(
 4390            editor.selections.ranges::<Point>(cx),
 4391            [
 4392                Point::new(0, 7)..Point::new(0, 7),
 4393                Point::new(1, 3)..Point::new(1, 3)
 4394            ]
 4395        );
 4396        editor
 4397    });
 4398}
 4399
 4400#[gpui::test]
 4401async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 4402    init_test(cx, |_| {});
 4403
 4404    let mut cx = EditorTestContext::new(cx).await;
 4405
 4406    let diff_base = r#"
 4407        Line 0
 4408        Line 1
 4409        Line 2
 4410        Line 3
 4411        "#
 4412    .unindent();
 4413
 4414    cx.set_state(
 4415        &r#"
 4416        ˇLine 0
 4417        Line 1
 4418        Line 2
 4419        Line 3
 4420        "#
 4421        .unindent(),
 4422    );
 4423
 4424    cx.set_head_text(&diff_base);
 4425    executor.run_until_parked();
 4426
 4427    // Join lines
 4428    cx.update_editor(|editor, window, cx| {
 4429        editor.join_lines(&JoinLines, window, cx);
 4430    });
 4431    executor.run_until_parked();
 4432
 4433    cx.assert_editor_state(
 4434        &r#"
 4435        Line 0ˇ Line 1
 4436        Line 2
 4437        Line 3
 4438        "#
 4439        .unindent(),
 4440    );
 4441    // Join again
 4442    cx.update_editor(|editor, window, cx| {
 4443        editor.join_lines(&JoinLines, window, cx);
 4444    });
 4445    executor.run_until_parked();
 4446
 4447    cx.assert_editor_state(
 4448        &r#"
 4449        Line 0 Line 1ˇ Line 2
 4450        Line 3
 4451        "#
 4452        .unindent(),
 4453    );
 4454}
 4455
 4456#[gpui::test]
 4457async fn test_custom_newlines_cause_no_false_positive_diffs(
 4458    executor: BackgroundExecutor,
 4459    cx: &mut TestAppContext,
 4460) {
 4461    init_test(cx, |_| {});
 4462    let mut cx = EditorTestContext::new(cx).await;
 4463    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 4464    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 4465    executor.run_until_parked();
 4466
 4467    cx.update_editor(|editor, window, cx| {
 4468        let snapshot = editor.snapshot(window, cx);
 4469        assert_eq!(
 4470            snapshot
 4471                .buffer_snapshot
 4472                .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
 4473                .collect::<Vec<_>>(),
 4474            Vec::new(),
 4475            "Should not have any diffs for files with custom newlines"
 4476        );
 4477    });
 4478}
 4479
 4480#[gpui::test]
 4481async fn test_manipulate_immutable_lines_with_single_selection(cx: &mut TestAppContext) {
 4482    init_test(cx, |_| {});
 4483
 4484    let mut cx = EditorTestContext::new(cx).await;
 4485
 4486    // Test sort_lines_case_insensitive()
 4487    cx.set_state(indoc! {"
 4488        «z
 4489        y
 4490        x
 4491        Z
 4492        Y
 4493        Xˇ»
 4494    "});
 4495    cx.update_editor(|e, window, cx| {
 4496        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 4497    });
 4498    cx.assert_editor_state(indoc! {"
 4499        «x
 4500        X
 4501        y
 4502        Y
 4503        z
 4504        Zˇ»
 4505    "});
 4506
 4507    // Test sort_lines_by_length()
 4508    //
 4509    // Demonstrates:
 4510    // - ∞ is 3 bytes UTF-8, but sorted by its char count (1)
 4511    // - sort is stable
 4512    cx.set_state(indoc! {"
 4513        «123
 4514        æ
 4515        12
 4516 4517        1
 4518        æˇ»
 4519    "});
 4520    cx.update_editor(|e, window, cx| e.sort_lines_by_length(&SortLinesByLength, window, cx));
 4521    cx.assert_editor_state(indoc! {"
 4522        «æ
 4523 4524        1
 4525        æ
 4526        12
 4527        123ˇ»
 4528    "});
 4529
 4530    // Test reverse_lines()
 4531    cx.set_state(indoc! {"
 4532        «5
 4533        4
 4534        3
 4535        2
 4536        1ˇ»
 4537    "});
 4538    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4539    cx.assert_editor_state(indoc! {"
 4540        «1
 4541        2
 4542        3
 4543        4
 4544        5ˇ»
 4545    "});
 4546
 4547    // Skip testing shuffle_line()
 4548
 4549    // From here on out, test more complex cases of manipulate_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 4550    // Since all methods calling manipulate_immutable_lines() are doing the exact same general thing (reordering lines)
 4551
 4552    // Don't manipulate when cursor is on single line, but expand the selection
 4553    cx.set_state(indoc! {"
 4554        ddˇdd
 4555        ccc
 4556        bb
 4557        a
 4558    "});
 4559    cx.update_editor(|e, window, cx| {
 4560        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4561    });
 4562    cx.assert_editor_state(indoc! {"
 4563        «ddddˇ»
 4564        ccc
 4565        bb
 4566        a
 4567    "});
 4568
 4569    // Basic manipulate case
 4570    // Start selection moves to column 0
 4571    // End of selection shrinks to fit shorter line
 4572    cx.set_state(indoc! {"
 4573        dd«d
 4574        ccc
 4575        bb
 4576        aaaaaˇ»
 4577    "});
 4578    cx.update_editor(|e, window, cx| {
 4579        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4580    });
 4581    cx.assert_editor_state(indoc! {"
 4582        «aaaaa
 4583        bb
 4584        ccc
 4585        dddˇ»
 4586    "});
 4587
 4588    // Manipulate case with newlines
 4589    cx.set_state(indoc! {"
 4590        dd«d
 4591        ccc
 4592
 4593        bb
 4594        aaaaa
 4595
 4596        ˇ»
 4597    "});
 4598    cx.update_editor(|e, window, cx| {
 4599        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4600    });
 4601    cx.assert_editor_state(indoc! {"
 4602        «
 4603
 4604        aaaaa
 4605        bb
 4606        ccc
 4607        dddˇ»
 4608
 4609    "});
 4610
 4611    // Adding new line
 4612    cx.set_state(indoc! {"
 4613        aa«a
 4614        bbˇ»b
 4615    "});
 4616    cx.update_editor(|e, window, cx| {
 4617        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added_line"))
 4618    });
 4619    cx.assert_editor_state(indoc! {"
 4620        «aaa
 4621        bbb
 4622        added_lineˇ»
 4623    "});
 4624
 4625    // Removing line
 4626    cx.set_state(indoc! {"
 4627        aa«a
 4628        bbbˇ»
 4629    "});
 4630    cx.update_editor(|e, window, cx| {
 4631        e.manipulate_immutable_lines(window, cx, |lines| {
 4632            lines.pop();
 4633        })
 4634    });
 4635    cx.assert_editor_state(indoc! {"
 4636        «aaaˇ»
 4637    "});
 4638
 4639    // Removing all lines
 4640    cx.set_state(indoc! {"
 4641        aa«a
 4642        bbbˇ»
 4643    "});
 4644    cx.update_editor(|e, window, cx| {
 4645        e.manipulate_immutable_lines(window, cx, |lines| {
 4646            lines.drain(..);
 4647        })
 4648    });
 4649    cx.assert_editor_state(indoc! {"
 4650        ˇ
 4651    "});
 4652}
 4653
 4654#[gpui::test]
 4655async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 4656    init_test(cx, |_| {});
 4657
 4658    let mut cx = EditorTestContext::new(cx).await;
 4659
 4660    // Consider continuous selection as single selection
 4661    cx.set_state(indoc! {"
 4662        Aaa«aa
 4663        cˇ»c«c
 4664        bb
 4665        aaaˇ»aa
 4666    "});
 4667    cx.update_editor(|e, window, cx| {
 4668        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4669    });
 4670    cx.assert_editor_state(indoc! {"
 4671        «Aaaaa
 4672        ccc
 4673        bb
 4674        aaaaaˇ»
 4675    "});
 4676
 4677    cx.set_state(indoc! {"
 4678        Aaa«aa
 4679        cˇ»c«c
 4680        bb
 4681        aaaˇ»aa
 4682    "});
 4683    cx.update_editor(|e, window, cx| {
 4684        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4685    });
 4686    cx.assert_editor_state(indoc! {"
 4687        «Aaaaa
 4688        ccc
 4689        bbˇ»
 4690    "});
 4691
 4692    // Consider non continuous selection as distinct dedup operations
 4693    cx.set_state(indoc! {"
 4694        «aaaaa
 4695        bb
 4696        aaaaa
 4697        aaaaaˇ»
 4698
 4699        aaa«aaˇ»
 4700    "});
 4701    cx.update_editor(|e, window, cx| {
 4702        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4703    });
 4704    cx.assert_editor_state(indoc! {"
 4705        «aaaaa
 4706        bbˇ»
 4707
 4708        «aaaaaˇ»
 4709    "});
 4710}
 4711
 4712#[gpui::test]
 4713async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 4714    init_test(cx, |_| {});
 4715
 4716    let mut cx = EditorTestContext::new(cx).await;
 4717
 4718    cx.set_state(indoc! {"
 4719        «Aaa
 4720        aAa
 4721        Aaaˇ»
 4722    "});
 4723    cx.update_editor(|e, window, cx| {
 4724        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4725    });
 4726    cx.assert_editor_state(indoc! {"
 4727        «Aaa
 4728        aAaˇ»
 4729    "});
 4730
 4731    cx.set_state(indoc! {"
 4732        «Aaa
 4733        aAa
 4734        aaAˇ»
 4735    "});
 4736    cx.update_editor(|e, window, cx| {
 4737        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4738    });
 4739    cx.assert_editor_state(indoc! {"
 4740        «Aaaˇ»
 4741    "});
 4742}
 4743
 4744#[gpui::test]
 4745async fn test_wrap_in_tag_single_selection(cx: &mut TestAppContext) {
 4746    init_test(cx, |_| {});
 4747
 4748    let mut cx = EditorTestContext::new(cx).await;
 4749
 4750    let js_language = Arc::new(Language::new(
 4751        LanguageConfig {
 4752            name: "JavaScript".into(),
 4753            wrap_characters: Some(language::WrapCharactersConfig {
 4754                start_prefix: "<".into(),
 4755                start_suffix: ">".into(),
 4756                end_prefix: "</".into(),
 4757                end_suffix: ">".into(),
 4758            }),
 4759            ..LanguageConfig::default()
 4760        },
 4761        None,
 4762    ));
 4763
 4764    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 4765
 4766    cx.set_state(indoc! {"
 4767        «testˇ»
 4768    "});
 4769    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4770    cx.assert_editor_state(indoc! {"
 4771        <«ˇ»>test</«ˇ»>
 4772    "});
 4773
 4774    cx.set_state(indoc! {"
 4775        «test
 4776         testˇ»
 4777    "});
 4778    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4779    cx.assert_editor_state(indoc! {"
 4780        <«ˇ»>test
 4781         test</«ˇ»>
 4782    "});
 4783
 4784    cx.set_state(indoc! {"
 4785        teˇst
 4786    "});
 4787    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4788    cx.assert_editor_state(indoc! {"
 4789        te<«ˇ»></«ˇ»>st
 4790    "});
 4791}
 4792
 4793#[gpui::test]
 4794async fn test_wrap_in_tag_multi_selection(cx: &mut TestAppContext) {
 4795    init_test(cx, |_| {});
 4796
 4797    let mut cx = EditorTestContext::new(cx).await;
 4798
 4799    let js_language = Arc::new(Language::new(
 4800        LanguageConfig {
 4801            name: "JavaScript".into(),
 4802            wrap_characters: Some(language::WrapCharactersConfig {
 4803                start_prefix: "<".into(),
 4804                start_suffix: ">".into(),
 4805                end_prefix: "</".into(),
 4806                end_suffix: ">".into(),
 4807            }),
 4808            ..LanguageConfig::default()
 4809        },
 4810        None,
 4811    ));
 4812
 4813    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 4814
 4815    cx.set_state(indoc! {"
 4816        «testˇ»
 4817        «testˇ» «testˇ»
 4818        «testˇ»
 4819    "});
 4820    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4821    cx.assert_editor_state(indoc! {"
 4822        <«ˇ»>test</«ˇ»>
 4823        <«ˇ»>test</«ˇ»> <«ˇ»>test</«ˇ»>
 4824        <«ˇ»>test</«ˇ»>
 4825    "});
 4826
 4827    cx.set_state(indoc! {"
 4828        «test
 4829         testˇ»
 4830        «test
 4831         testˇ»
 4832    "});
 4833    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4834    cx.assert_editor_state(indoc! {"
 4835        <«ˇ»>test
 4836         test</«ˇ»>
 4837        <«ˇ»>test
 4838         test</«ˇ»>
 4839    "});
 4840}
 4841
 4842#[gpui::test]
 4843async fn test_wrap_in_tag_does_nothing_in_unsupported_languages(cx: &mut TestAppContext) {
 4844    init_test(cx, |_| {});
 4845
 4846    let mut cx = EditorTestContext::new(cx).await;
 4847
 4848    let plaintext_language = Arc::new(Language::new(
 4849        LanguageConfig {
 4850            name: "Plain Text".into(),
 4851            ..LanguageConfig::default()
 4852        },
 4853        None,
 4854    ));
 4855
 4856    cx.update_buffer(|buffer, cx| buffer.set_language(Some(plaintext_language), cx));
 4857
 4858    cx.set_state(indoc! {"
 4859        «testˇ»
 4860    "});
 4861    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4862    cx.assert_editor_state(indoc! {"
 4863      «testˇ»
 4864    "});
 4865}
 4866
 4867#[gpui::test]
 4868async fn test_manipulate_immutable_lines_with_multi_selection(cx: &mut TestAppContext) {
 4869    init_test(cx, |_| {});
 4870
 4871    let mut cx = EditorTestContext::new(cx).await;
 4872
 4873    // Manipulate with multiple selections on a single line
 4874    cx.set_state(indoc! {"
 4875        dd«dd
 4876        cˇ»c«c
 4877        bb
 4878        aaaˇ»aa
 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        «aaaaa
 4885        bb
 4886        ccc
 4887        ddddˇ»
 4888    "});
 4889
 4890    // Manipulate with multiple disjoin selections
 4891    cx.set_state(indoc! {"
 4892 4893        4
 4894        3
 4895        2
 4896        1ˇ»
 4897
 4898        dd«dd
 4899        ccc
 4900        bb
 4901        aaaˇ»aa
 4902    "});
 4903    cx.update_editor(|e, window, cx| {
 4904        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4905    });
 4906    cx.assert_editor_state(indoc! {"
 4907        «1
 4908        2
 4909        3
 4910        4
 4911        5ˇ»
 4912
 4913        «aaaaa
 4914        bb
 4915        ccc
 4916        ddddˇ»
 4917    "});
 4918
 4919    // Adding lines on each selection
 4920    cx.set_state(indoc! {"
 4921 4922        1ˇ»
 4923
 4924        bb«bb
 4925        aaaˇ»aa
 4926    "});
 4927    cx.update_editor(|e, window, cx| {
 4928        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added line"))
 4929    });
 4930    cx.assert_editor_state(indoc! {"
 4931        «2
 4932        1
 4933        added lineˇ»
 4934
 4935        «bbbb
 4936        aaaaa
 4937        added lineˇ»
 4938    "});
 4939
 4940    // Removing lines on each selection
 4941    cx.set_state(indoc! {"
 4942 4943        1ˇ»
 4944
 4945        bb«bb
 4946        aaaˇ»aa
 4947    "});
 4948    cx.update_editor(|e, window, cx| {
 4949        e.manipulate_immutable_lines(window, cx, |lines| {
 4950            lines.pop();
 4951        })
 4952    });
 4953    cx.assert_editor_state(indoc! {"
 4954        «2ˇ»
 4955
 4956        «bbbbˇ»
 4957    "});
 4958}
 4959
 4960#[gpui::test]
 4961async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 4962    init_test(cx, |settings| {
 4963        settings.defaults.tab_size = NonZeroU32::new(3)
 4964    });
 4965
 4966    let mut cx = EditorTestContext::new(cx).await;
 4967
 4968    // MULTI SELECTION
 4969    // Ln.1 "«" tests empty lines
 4970    // Ln.9 tests just leading whitespace
 4971    cx.set_state(indoc! {"
 4972        «
 4973        abc                 // No indentationˇ»
 4974        «\tabc              // 1 tabˇ»
 4975        \t\tabc «      ˇ»   // 2 tabs
 4976        \t ab«c             // Tab followed by space
 4977         \tabc              // Space followed by tab (3 spaces should be the result)
 4978        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4979           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 4980        \t
 4981        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4982    "});
 4983    cx.update_editor(|e, window, cx| {
 4984        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4985    });
 4986    cx.assert_editor_state(
 4987        indoc! {"
 4988            «
 4989            abc                 // No indentation
 4990               abc              // 1 tab
 4991                  abc          // 2 tabs
 4992                abc             // Tab followed by space
 4993               abc              // Space followed by tab (3 spaces should be the result)
 4994                           abc   // Mixed indentation (tab conversion depends on the column)
 4995               abc         // Already space indented
 4996               ·
 4997               abc\tdef          // Only the leading tab is manipulatedˇ»
 4998        "}
 4999        .replace("·", "")
 5000        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5001    );
 5002
 5003    // Test on just a few lines, the others should remain unchanged
 5004    // Only lines (3, 5, 10, 11) should change
 5005    cx.set_state(
 5006        indoc! {"
 5007            ·
 5008            abc                 // No indentation
 5009            \tabcˇ               // 1 tab
 5010            \t\tabc             // 2 tabs
 5011            \t abcˇ              // Tab followed by space
 5012             \tabc              // Space followed by tab (3 spaces should be the result)
 5013            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5014               abc              // Already space indented
 5015            «\t
 5016            \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5017        "}
 5018        .replace("·", "")
 5019        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5020    );
 5021    cx.update_editor(|e, window, cx| {
 5022        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5023    });
 5024    cx.assert_editor_state(
 5025        indoc! {"
 5026            ·
 5027            abc                 // No indentation
 5028            «   abc               // 1 tabˇ»
 5029            \t\tabc             // 2 tabs
 5030            «    abc              // Tab followed by spaceˇ»
 5031             \tabc              // Space followed by tab (3 spaces should be the result)
 5032            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5033               abc              // Already space indented
 5034            «   ·
 5035               abc\tdef          // Only the leading tab is manipulatedˇ»
 5036        "}
 5037        .replace("·", "")
 5038        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5039    );
 5040
 5041    // SINGLE SELECTION
 5042    // Ln.1 "«" tests empty lines
 5043    // Ln.9 tests just leading whitespace
 5044    cx.set_state(indoc! {"
 5045        «
 5046        abc                 // No indentation
 5047        \tabc               // 1 tab
 5048        \t\tabc             // 2 tabs
 5049        \t abc              // Tab followed by space
 5050         \tabc              // Space followed by tab (3 spaces should be the result)
 5051        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5052           abc              // Already space indented
 5053        \t
 5054        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5055    "});
 5056    cx.update_editor(|e, window, cx| {
 5057        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5058    });
 5059    cx.assert_editor_state(
 5060        indoc! {"
 5061            «
 5062            abc                 // No indentation
 5063               abc               // 1 tab
 5064                  abc             // 2 tabs
 5065                abc              // Tab followed by space
 5066               abc              // Space followed by tab (3 spaces should be the result)
 5067                           abc   // Mixed indentation (tab conversion depends on the column)
 5068               abc              // Already space indented
 5069               ·
 5070               abc\tdef          // Only the leading tab is manipulatedˇ»
 5071        "}
 5072        .replace("·", "")
 5073        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5074    );
 5075}
 5076
 5077#[gpui::test]
 5078async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 5079    init_test(cx, |settings| {
 5080        settings.defaults.tab_size = NonZeroU32::new(3)
 5081    });
 5082
 5083    let mut cx = EditorTestContext::new(cx).await;
 5084
 5085    // MULTI SELECTION
 5086    // Ln.1 "«" tests empty lines
 5087    // Ln.11 tests just leading whitespace
 5088    cx.set_state(indoc! {"
 5089        «
 5090        abˇ»ˇc                 // No indentation
 5091         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 5092          abc  «             // 2 spaces (< 3 so dont convert)
 5093           abc              // 3 spaces (convert)
 5094             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 5095        «\tˇ»\t«\tˇ»abc           // Already tab indented
 5096        «\t abc              // Tab followed by space
 5097         \tabc              // Space followed by tab (should be consumed due to tab)
 5098        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5099           \tˇ»  «\t
 5100           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 5101    "});
 5102    cx.update_editor(|e, window, cx| {
 5103        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5104    });
 5105    cx.assert_editor_state(indoc! {"
 5106        «
 5107        abc                 // No indentation
 5108         abc                // 1 space (< 3 so dont convert)
 5109          abc               // 2 spaces (< 3 so dont convert)
 5110        \tabc              // 3 spaces (convert)
 5111        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5112        \t\t\tabc           // Already tab indented
 5113        \t abc              // Tab followed by space
 5114        \tabc              // Space followed by tab (should be consumed due to tab)
 5115        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5116        \t\t\t
 5117        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5118    "});
 5119
 5120    // Test on just a few lines, the other should remain unchanged
 5121    // Only lines (4, 8, 11, 12) should change
 5122    cx.set_state(
 5123        indoc! {"
 5124            ·
 5125            abc                 // No indentation
 5126             abc                // 1 space (< 3 so dont convert)
 5127              abc               // 2 spaces (< 3 so dont convert)
 5128            «   abc              // 3 spaces (convert)ˇ»
 5129                 abc            // 5 spaces (1 tab + 2 spaces)
 5130            \t\t\tabc           // Already tab indented
 5131            \t abc              // Tab followed by space
 5132             \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 5133               \t\t  \tabc      // Mixed indentation
 5134            \t \t  \t   \tabc   // Mixed indentation
 5135               \t  \tˇ
 5136            «   abc   \t         // Only the leading spaces should be convertedˇ»
 5137        "}
 5138        .replace("·", "")
 5139        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5140    );
 5141    cx.update_editor(|e, window, cx| {
 5142        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5143    });
 5144    cx.assert_editor_state(
 5145        indoc! {"
 5146            ·
 5147            abc                 // No indentation
 5148             abc                // 1 space (< 3 so dont convert)
 5149              abc               // 2 spaces (< 3 so dont convert)
 5150            «\tabc              // 3 spaces (convert)ˇ»
 5151                 abc            // 5 spaces (1 tab + 2 spaces)
 5152            \t\t\tabc           // Already tab indented
 5153            \t abc              // Tab followed by space
 5154            «\tabc              // Space followed by tab (should be consumed due to tab)ˇ»
 5155               \t\t  \tabc      // Mixed indentation
 5156            \t \t  \t   \tabc   // Mixed indentation
 5157            «\t\t\t
 5158            \tabc   \t         // Only the leading spaces should be convertedˇ»
 5159        "}
 5160        .replace("·", "")
 5161        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5162    );
 5163
 5164    // SINGLE SELECTION
 5165    // Ln.1 "«" tests empty lines
 5166    // Ln.11 tests just leading whitespace
 5167    cx.set_state(indoc! {"
 5168        «
 5169        abc                 // No indentation
 5170         abc                // 1 space (< 3 so dont convert)
 5171          abc               // 2 spaces (< 3 so dont convert)
 5172           abc              // 3 spaces (convert)
 5173             abc            // 5 spaces (1 tab + 2 spaces)
 5174        \t\t\tabc           // Already tab indented
 5175        \t abc              // Tab followed by space
 5176         \tabc              // Space followed by tab (should be consumed due to tab)
 5177        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5178           \t  \t
 5179           abc   \t         // Only the leading spaces should be convertedˇ»
 5180    "});
 5181    cx.update_editor(|e, window, cx| {
 5182        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5183    });
 5184    cx.assert_editor_state(indoc! {"
 5185        «
 5186        abc                 // No indentation
 5187         abc                // 1 space (< 3 so dont convert)
 5188          abc               // 2 spaces (< 3 so dont convert)
 5189        \tabc              // 3 spaces (convert)
 5190        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5191        \t\t\tabc           // Already tab indented
 5192        \t abc              // Tab followed by space
 5193        \tabc              // Space followed by tab (should be consumed due to tab)
 5194        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5195        \t\t\t
 5196        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5197    "});
 5198}
 5199
 5200#[gpui::test]
 5201async fn test_toggle_case(cx: &mut TestAppContext) {
 5202    init_test(cx, |_| {});
 5203
 5204    let mut cx = EditorTestContext::new(cx).await;
 5205
 5206    // If all lower case -> upper case
 5207    cx.set_state(indoc! {"
 5208        «hello worldˇ»
 5209    "});
 5210    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5211    cx.assert_editor_state(indoc! {"
 5212        «HELLO WORLDˇ»
 5213    "});
 5214
 5215    // If all upper case -> lower case
 5216    cx.set_state(indoc! {"
 5217        «HELLO WORLDˇ»
 5218    "});
 5219    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5220    cx.assert_editor_state(indoc! {"
 5221        «hello worldˇ»
 5222    "});
 5223
 5224    // If any upper case characters are identified -> lower case
 5225    // This matches JetBrains IDEs
 5226    cx.set_state(indoc! {"
 5227        «hEllo worldˇ»
 5228    "});
 5229    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5230    cx.assert_editor_state(indoc! {"
 5231        «hello worldˇ»
 5232    "});
 5233}
 5234
 5235#[gpui::test]
 5236async fn test_convert_to_sentence_case(cx: &mut TestAppContext) {
 5237    init_test(cx, |_| {});
 5238
 5239    let mut cx = EditorTestContext::new(cx).await;
 5240
 5241    cx.set_state(indoc! {"
 5242        «implement-windows-supportˇ»
 5243    "});
 5244    cx.update_editor(|e, window, cx| {
 5245        e.convert_to_sentence_case(&ConvertToSentenceCase, window, cx)
 5246    });
 5247    cx.assert_editor_state(indoc! {"
 5248        «Implement windows supportˇ»
 5249    "});
 5250}
 5251
 5252#[gpui::test]
 5253async fn test_manipulate_text(cx: &mut TestAppContext) {
 5254    init_test(cx, |_| {});
 5255
 5256    let mut cx = EditorTestContext::new(cx).await;
 5257
 5258    // Test convert_to_upper_case()
 5259    cx.set_state(indoc! {"
 5260        «hello worldˇ»
 5261    "});
 5262    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5263    cx.assert_editor_state(indoc! {"
 5264        «HELLO WORLDˇ»
 5265    "});
 5266
 5267    // Test convert_to_lower_case()
 5268    cx.set_state(indoc! {"
 5269        «HELLO WORLDˇ»
 5270    "});
 5271    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 5272    cx.assert_editor_state(indoc! {"
 5273        «hello worldˇ»
 5274    "});
 5275
 5276    // Test multiple line, single selection case
 5277    cx.set_state(indoc! {"
 5278        «The quick brown
 5279        fox jumps over
 5280        the lazy dogˇ»
 5281    "});
 5282    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 5283    cx.assert_editor_state(indoc! {"
 5284        «The Quick Brown
 5285        Fox Jumps Over
 5286        The Lazy Dogˇ»
 5287    "});
 5288
 5289    // Test multiple line, single selection case
 5290    cx.set_state(indoc! {"
 5291        «The quick brown
 5292        fox jumps over
 5293        the lazy dogˇ»
 5294    "});
 5295    cx.update_editor(|e, window, cx| {
 5296        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 5297    });
 5298    cx.assert_editor_state(indoc! {"
 5299        «TheQuickBrown
 5300        FoxJumpsOver
 5301        TheLazyDogˇ»
 5302    "});
 5303
 5304    // From here on out, test more complex cases of manipulate_text()
 5305
 5306    // Test no selection case - should affect words cursors are in
 5307    // Cursor at beginning, middle, and end of word
 5308    cx.set_state(indoc! {"
 5309        ˇhello big beauˇtiful worldˇ
 5310    "});
 5311    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5312    cx.assert_editor_state(indoc! {"
 5313        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 5314    "});
 5315
 5316    // Test multiple selections on a single line and across multiple lines
 5317    cx.set_state(indoc! {"
 5318        «Theˇ» quick «brown
 5319        foxˇ» jumps «overˇ»
 5320        the «lazyˇ» dog
 5321    "});
 5322    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5323    cx.assert_editor_state(indoc! {"
 5324        «THEˇ» quick «BROWN
 5325        FOXˇ» jumps «OVERˇ»
 5326        the «LAZYˇ» dog
 5327    "});
 5328
 5329    // Test case where text length grows
 5330    cx.set_state(indoc! {"
 5331        «tschüߡ»
 5332    "});
 5333    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5334    cx.assert_editor_state(indoc! {"
 5335        «TSCHÜSSˇ»
 5336    "});
 5337
 5338    // Test to make sure we don't crash when text shrinks
 5339    cx.set_state(indoc! {"
 5340        aaa_bbbˇ
 5341    "});
 5342    cx.update_editor(|e, window, cx| {
 5343        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5344    });
 5345    cx.assert_editor_state(indoc! {"
 5346        «aaaBbbˇ»
 5347    "});
 5348
 5349    // Test to make sure we all aware of the fact that each word can grow and shrink
 5350    // Final selections should be aware of this fact
 5351    cx.set_state(indoc! {"
 5352        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 5353    "});
 5354    cx.update_editor(|e, window, cx| {
 5355        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5356    });
 5357    cx.assert_editor_state(indoc! {"
 5358        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 5359    "});
 5360
 5361    cx.set_state(indoc! {"
 5362        «hElLo, WoRld!ˇ»
 5363    "});
 5364    cx.update_editor(|e, window, cx| {
 5365        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 5366    });
 5367    cx.assert_editor_state(indoc! {"
 5368        «HeLlO, wOrLD!ˇ»
 5369    "});
 5370
 5371    // Test selections with `line_mode = true`.
 5372    cx.update_editor(|editor, _window, _cx| editor.selections.line_mode = true);
 5373    cx.set_state(indoc! {"
 5374        «The quick brown
 5375        fox jumps over
 5376        tˇ»he lazy dog
 5377    "});
 5378    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5379    cx.assert_editor_state(indoc! {"
 5380        «THE QUICK BROWN
 5381        FOX JUMPS OVER
 5382        THE LAZY DOGˇ»
 5383    "});
 5384}
 5385
 5386#[gpui::test]
 5387fn test_duplicate_line(cx: &mut TestAppContext) {
 5388    init_test(cx, |_| {});
 5389
 5390    let editor = cx.add_window(|window, cx| {
 5391        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5392        build_editor(buffer, window, cx)
 5393    });
 5394    _ = editor.update(cx, |editor, window, cx| {
 5395        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5396            s.select_display_ranges([
 5397                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5398                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5399                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5400                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5401            ])
 5402        });
 5403        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5404        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5405        assert_eq!(
 5406            editor.selections.display_ranges(cx),
 5407            vec![
 5408                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 5409                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 5410                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5411                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 5412            ]
 5413        );
 5414    });
 5415
 5416    let editor = cx.add_window(|window, cx| {
 5417        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5418        build_editor(buffer, window, cx)
 5419    });
 5420    _ = editor.update(cx, |editor, window, cx| {
 5421        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5422            s.select_display_ranges([
 5423                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5424                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5425            ])
 5426        });
 5427        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5428        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5429        assert_eq!(
 5430            editor.selections.display_ranges(cx),
 5431            vec![
 5432                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 5433                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 5434            ]
 5435        );
 5436    });
 5437
 5438    // With `move_upwards` the selections stay in place, except for
 5439    // the lines inserted above them
 5440    let editor = cx.add_window(|window, cx| {
 5441        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5442        build_editor(buffer, window, cx)
 5443    });
 5444    _ = editor.update(cx, |editor, window, cx| {
 5445        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5446            s.select_display_ranges([
 5447                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5448                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5449                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5450                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5451            ])
 5452        });
 5453        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5454        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5455        assert_eq!(
 5456            editor.selections.display_ranges(cx),
 5457            vec![
 5458                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5459                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5460                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 5461                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 5462            ]
 5463        );
 5464    });
 5465
 5466    let editor = cx.add_window(|window, cx| {
 5467        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5468        build_editor(buffer, window, cx)
 5469    });
 5470    _ = editor.update(cx, |editor, window, cx| {
 5471        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5472            s.select_display_ranges([
 5473                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5474                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5475            ])
 5476        });
 5477        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5478        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5479        assert_eq!(
 5480            editor.selections.display_ranges(cx),
 5481            vec![
 5482                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5483                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5484            ]
 5485        );
 5486    });
 5487
 5488    let editor = cx.add_window(|window, cx| {
 5489        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5490        build_editor(buffer, window, cx)
 5491    });
 5492    _ = editor.update(cx, |editor, window, cx| {
 5493        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5494            s.select_display_ranges([
 5495                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5496                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5497            ])
 5498        });
 5499        editor.duplicate_selection(&DuplicateSelection, window, cx);
 5500        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 5501        assert_eq!(
 5502            editor.selections.display_ranges(cx),
 5503            vec![
 5504                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5505                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 5506            ]
 5507        );
 5508    });
 5509}
 5510
 5511#[gpui::test]
 5512fn test_move_line_up_down(cx: &mut TestAppContext) {
 5513    init_test(cx, |_| {});
 5514
 5515    let editor = cx.add_window(|window, cx| {
 5516        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5517        build_editor(buffer, window, cx)
 5518    });
 5519    _ = editor.update(cx, |editor, window, cx| {
 5520        editor.fold_creases(
 5521            vec![
 5522                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 5523                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 5524                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 5525            ],
 5526            true,
 5527            window,
 5528            cx,
 5529        );
 5530        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5531            s.select_display_ranges([
 5532                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5533                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5534                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5535                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 5536            ])
 5537        });
 5538        assert_eq!(
 5539            editor.display_text(cx),
 5540            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 5541        );
 5542
 5543        editor.move_line_up(&MoveLineUp, window, cx);
 5544        assert_eq!(
 5545            editor.display_text(cx),
 5546            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 5547        );
 5548        assert_eq!(
 5549            editor.selections.display_ranges(cx),
 5550            vec![
 5551                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5552                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5553                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5554                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5555            ]
 5556        );
 5557    });
 5558
 5559    _ = editor.update(cx, |editor, window, cx| {
 5560        editor.move_line_down(&MoveLineDown, window, cx);
 5561        assert_eq!(
 5562            editor.display_text(cx),
 5563            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 5564        );
 5565        assert_eq!(
 5566            editor.selections.display_ranges(cx),
 5567            vec![
 5568                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5569                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5570                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5571                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5572            ]
 5573        );
 5574    });
 5575
 5576    _ = editor.update(cx, |editor, window, cx| {
 5577        editor.move_line_down(&MoveLineDown, window, cx);
 5578        assert_eq!(
 5579            editor.display_text(cx),
 5580            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 5581        );
 5582        assert_eq!(
 5583            editor.selections.display_ranges(cx),
 5584            vec![
 5585                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5586                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5587                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5588                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5589            ]
 5590        );
 5591    });
 5592
 5593    _ = editor.update(cx, |editor, window, cx| {
 5594        editor.move_line_up(&MoveLineUp, window, cx);
 5595        assert_eq!(
 5596            editor.display_text(cx),
 5597            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 5598        );
 5599        assert_eq!(
 5600            editor.selections.display_ranges(cx),
 5601            vec![
 5602                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5603                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5604                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5605                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5606            ]
 5607        );
 5608    });
 5609}
 5610
 5611#[gpui::test]
 5612fn test_move_line_up_selection_at_end_of_fold(cx: &mut TestAppContext) {
 5613    init_test(cx, |_| {});
 5614    let editor = cx.add_window(|window, cx| {
 5615        let buffer = MultiBuffer::build_simple("\n\n\n\n\n\naaaa\nbbbb\ncccc", cx);
 5616        build_editor(buffer, window, cx)
 5617    });
 5618    _ = editor.update(cx, |editor, window, cx| {
 5619        editor.fold_creases(
 5620            vec![Crease::simple(
 5621                Point::new(6, 4)..Point::new(7, 4),
 5622                FoldPlaceholder::test(),
 5623            )],
 5624            true,
 5625            window,
 5626            cx,
 5627        );
 5628        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5629            s.select_ranges([Point::new(7, 4)..Point::new(7, 4)])
 5630        });
 5631        assert_eq!(editor.display_text(cx), "\n\n\n\n\n\naaaa⋯\ncccc");
 5632        editor.move_line_up(&MoveLineUp, window, cx);
 5633        let buffer_text = editor.buffer.read(cx).snapshot(cx).text();
 5634        assert_eq!(buffer_text, "\n\n\n\n\naaaa\nbbbb\n\ncccc");
 5635    });
 5636}
 5637
 5638#[gpui::test]
 5639fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 5640    init_test(cx, |_| {});
 5641
 5642    let editor = cx.add_window(|window, cx| {
 5643        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5644        build_editor(buffer, window, cx)
 5645    });
 5646    _ = editor.update(cx, |editor, window, cx| {
 5647        let snapshot = editor.buffer.read(cx).snapshot(cx);
 5648        editor.insert_blocks(
 5649            [BlockProperties {
 5650                style: BlockStyle::Fixed,
 5651                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 5652                height: Some(1),
 5653                render: Arc::new(|_| div().into_any()),
 5654                priority: 0,
 5655            }],
 5656            Some(Autoscroll::fit()),
 5657            cx,
 5658        );
 5659        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5660            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 5661        });
 5662        editor.move_line_down(&MoveLineDown, window, cx);
 5663    });
 5664}
 5665
 5666#[gpui::test]
 5667async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 5668    init_test(cx, |_| {});
 5669
 5670    let mut cx = EditorTestContext::new(cx).await;
 5671    cx.set_state(
 5672        &"
 5673            ˇzero
 5674            one
 5675            two
 5676            three
 5677            four
 5678            five
 5679        "
 5680        .unindent(),
 5681    );
 5682
 5683    // Create a four-line block that replaces three lines of text.
 5684    cx.update_editor(|editor, window, cx| {
 5685        let snapshot = editor.snapshot(window, cx);
 5686        let snapshot = &snapshot.buffer_snapshot;
 5687        let placement = BlockPlacement::Replace(
 5688            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 5689        );
 5690        editor.insert_blocks(
 5691            [BlockProperties {
 5692                placement,
 5693                height: Some(4),
 5694                style: BlockStyle::Sticky,
 5695                render: Arc::new(|_| gpui::div().into_any_element()),
 5696                priority: 0,
 5697            }],
 5698            None,
 5699            cx,
 5700        );
 5701    });
 5702
 5703    // Move down so that the cursor touches the block.
 5704    cx.update_editor(|editor, window, cx| {
 5705        editor.move_down(&Default::default(), window, cx);
 5706    });
 5707    cx.assert_editor_state(
 5708        &"
 5709            zero
 5710            «one
 5711            two
 5712            threeˇ»
 5713            four
 5714            five
 5715        "
 5716        .unindent(),
 5717    );
 5718
 5719    // Move down past the block.
 5720    cx.update_editor(|editor, window, cx| {
 5721        editor.move_down(&Default::default(), window, cx);
 5722    });
 5723    cx.assert_editor_state(
 5724        &"
 5725            zero
 5726            one
 5727            two
 5728            three
 5729            ˇfour
 5730            five
 5731        "
 5732        .unindent(),
 5733    );
 5734}
 5735
 5736#[gpui::test]
 5737fn test_transpose(cx: &mut TestAppContext) {
 5738    init_test(cx, |_| {});
 5739
 5740    _ = cx.add_window(|window, cx| {
 5741        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 5742        editor.set_style(EditorStyle::default(), window, cx);
 5743        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5744            s.select_ranges([1..1])
 5745        });
 5746        editor.transpose(&Default::default(), window, cx);
 5747        assert_eq!(editor.text(cx), "bac");
 5748        assert_eq!(editor.selections.ranges(cx), [2..2]);
 5749
 5750        editor.transpose(&Default::default(), window, cx);
 5751        assert_eq!(editor.text(cx), "bca");
 5752        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5753
 5754        editor.transpose(&Default::default(), window, cx);
 5755        assert_eq!(editor.text(cx), "bac");
 5756        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5757
 5758        editor
 5759    });
 5760
 5761    _ = cx.add_window(|window, cx| {
 5762        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5763        editor.set_style(EditorStyle::default(), window, cx);
 5764        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5765            s.select_ranges([3..3])
 5766        });
 5767        editor.transpose(&Default::default(), window, cx);
 5768        assert_eq!(editor.text(cx), "acb\nde");
 5769        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5770
 5771        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5772            s.select_ranges([4..4])
 5773        });
 5774        editor.transpose(&Default::default(), window, cx);
 5775        assert_eq!(editor.text(cx), "acbd\ne");
 5776        assert_eq!(editor.selections.ranges(cx), [5..5]);
 5777
 5778        editor.transpose(&Default::default(), window, cx);
 5779        assert_eq!(editor.text(cx), "acbde\n");
 5780        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5781
 5782        editor.transpose(&Default::default(), window, cx);
 5783        assert_eq!(editor.text(cx), "acbd\ne");
 5784        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5785
 5786        editor
 5787    });
 5788
 5789    _ = cx.add_window(|window, cx| {
 5790        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5791        editor.set_style(EditorStyle::default(), window, cx);
 5792        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5793            s.select_ranges([1..1, 2..2, 4..4])
 5794        });
 5795        editor.transpose(&Default::default(), window, cx);
 5796        assert_eq!(editor.text(cx), "bacd\ne");
 5797        assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
 5798
 5799        editor.transpose(&Default::default(), window, cx);
 5800        assert_eq!(editor.text(cx), "bcade\n");
 5801        assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
 5802
 5803        editor.transpose(&Default::default(), window, cx);
 5804        assert_eq!(editor.text(cx), "bcda\ne");
 5805        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5806
 5807        editor.transpose(&Default::default(), window, cx);
 5808        assert_eq!(editor.text(cx), "bcade\n");
 5809        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5810
 5811        editor.transpose(&Default::default(), window, cx);
 5812        assert_eq!(editor.text(cx), "bcaed\n");
 5813        assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
 5814
 5815        editor
 5816    });
 5817
 5818    _ = cx.add_window(|window, cx| {
 5819        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 5820        editor.set_style(EditorStyle::default(), window, cx);
 5821        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5822            s.select_ranges([4..4])
 5823        });
 5824        editor.transpose(&Default::default(), window, cx);
 5825        assert_eq!(editor.text(cx), "🏀🍐✋");
 5826        assert_eq!(editor.selections.ranges(cx), [8..8]);
 5827
 5828        editor.transpose(&Default::default(), window, cx);
 5829        assert_eq!(editor.text(cx), "🏀✋🍐");
 5830        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5831
 5832        editor.transpose(&Default::default(), window, cx);
 5833        assert_eq!(editor.text(cx), "🏀🍐✋");
 5834        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5835
 5836        editor
 5837    });
 5838}
 5839
 5840#[gpui::test]
 5841async fn test_rewrap(cx: &mut TestAppContext) {
 5842    init_test(cx, |settings| {
 5843        settings.languages.0.extend([
 5844            (
 5845                "Markdown".into(),
 5846                LanguageSettingsContent {
 5847                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5848                    preferred_line_length: Some(40),
 5849                    ..Default::default()
 5850                },
 5851            ),
 5852            (
 5853                "Plain Text".into(),
 5854                LanguageSettingsContent {
 5855                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5856                    preferred_line_length: Some(40),
 5857                    ..Default::default()
 5858                },
 5859            ),
 5860            (
 5861                "C++".into(),
 5862                LanguageSettingsContent {
 5863                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5864                    preferred_line_length: Some(40),
 5865                    ..Default::default()
 5866                },
 5867            ),
 5868            (
 5869                "Python".into(),
 5870                LanguageSettingsContent {
 5871                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5872                    preferred_line_length: Some(40),
 5873                    ..Default::default()
 5874                },
 5875            ),
 5876            (
 5877                "Rust".into(),
 5878                LanguageSettingsContent {
 5879                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5880                    preferred_line_length: Some(40),
 5881                    ..Default::default()
 5882                },
 5883            ),
 5884        ])
 5885    });
 5886
 5887    let mut cx = EditorTestContext::new(cx).await;
 5888
 5889    let cpp_language = Arc::new(Language::new(
 5890        LanguageConfig {
 5891            name: "C++".into(),
 5892            line_comments: vec!["// ".into()],
 5893            ..LanguageConfig::default()
 5894        },
 5895        None,
 5896    ));
 5897    let python_language = Arc::new(Language::new(
 5898        LanguageConfig {
 5899            name: "Python".into(),
 5900            line_comments: vec!["# ".into()],
 5901            ..LanguageConfig::default()
 5902        },
 5903        None,
 5904    ));
 5905    let markdown_language = Arc::new(Language::new(
 5906        LanguageConfig {
 5907            name: "Markdown".into(),
 5908            rewrap_prefixes: vec![
 5909                regex::Regex::new("\\d+\\.\\s+").unwrap(),
 5910                regex::Regex::new("[-*+]\\s+").unwrap(),
 5911            ],
 5912            ..LanguageConfig::default()
 5913        },
 5914        None,
 5915    ));
 5916    let rust_language = Arc::new(
 5917        Language::new(
 5918            LanguageConfig {
 5919                name: "Rust".into(),
 5920                line_comments: vec!["// ".into(), "/// ".into()],
 5921                ..LanguageConfig::default()
 5922            },
 5923            Some(tree_sitter_rust::LANGUAGE.into()),
 5924        )
 5925        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 5926        .unwrap(),
 5927    );
 5928
 5929    let plaintext_language = Arc::new(Language::new(
 5930        LanguageConfig {
 5931            name: "Plain Text".into(),
 5932            ..LanguageConfig::default()
 5933        },
 5934        None,
 5935    ));
 5936
 5937    // Test basic rewrapping of a long line with a cursor
 5938    assert_rewrap(
 5939        indoc! {"
 5940            // ˇThis is a long comment that needs to be wrapped.
 5941        "},
 5942        indoc! {"
 5943            // ˇThis is a long comment that needs to
 5944            // be wrapped.
 5945        "},
 5946        cpp_language.clone(),
 5947        &mut cx,
 5948    );
 5949
 5950    // Test rewrapping a full selection
 5951    assert_rewrap(
 5952        indoc! {"
 5953            «// This selected long comment needs to be wrapped.ˇ»"
 5954        },
 5955        indoc! {"
 5956            «// This selected long comment needs to
 5957            // be wrapped.ˇ»"
 5958        },
 5959        cpp_language.clone(),
 5960        &mut cx,
 5961    );
 5962
 5963    // Test multiple cursors on different lines within the same paragraph are preserved after rewrapping
 5964    assert_rewrap(
 5965        indoc! {"
 5966            // ˇThis is the first line.
 5967            // Thisˇ is the second line.
 5968            // This is the thirdˇ line, all part of one paragraph.
 5969         "},
 5970        indoc! {"
 5971            // ˇThis is the first line. Thisˇ is the
 5972            // second line. This is the thirdˇ line,
 5973            // all part of one paragraph.
 5974         "},
 5975        cpp_language.clone(),
 5976        &mut cx,
 5977    );
 5978
 5979    // Test multiple cursors in different paragraphs trigger separate rewraps
 5980    assert_rewrap(
 5981        indoc! {"
 5982            // ˇThis is the first paragraph, first line.
 5983            // ˇThis is the first paragraph, second line.
 5984
 5985            // ˇThis is the second paragraph, first line.
 5986            // ˇThis is the second paragraph, second line.
 5987        "},
 5988        indoc! {"
 5989            // ˇThis is the first paragraph, first
 5990            // line. ˇThis is the first paragraph,
 5991            // second line.
 5992
 5993            // ˇThis is the second paragraph, first
 5994            // line. ˇThis is the second paragraph,
 5995            // second line.
 5996        "},
 5997        cpp_language.clone(),
 5998        &mut cx,
 5999    );
 6000
 6001    // Test that change in comment prefix (e.g., `//` to `///`) trigger seperate rewraps
 6002    assert_rewrap(
 6003        indoc! {"
 6004            «// A regular long long comment to be wrapped.
 6005            /// A documentation long comment to be wrapped.ˇ»
 6006          "},
 6007        indoc! {"
 6008            «// A regular long long comment to be
 6009            // wrapped.
 6010            /// A documentation long comment to be
 6011            /// wrapped.ˇ»
 6012          "},
 6013        rust_language.clone(),
 6014        &mut cx,
 6015    );
 6016
 6017    // Test that change in indentation level trigger seperate rewraps
 6018    assert_rewrap(
 6019        indoc! {"
 6020            fn foo() {
 6021                «// This is a long comment at the base indent.
 6022                    // This is a long comment at the next indent.ˇ»
 6023            }
 6024        "},
 6025        indoc! {"
 6026            fn foo() {
 6027                «// This is a long comment at the
 6028                // base indent.
 6029                    // This is a long comment at the
 6030                    // next indent.ˇ»
 6031            }
 6032        "},
 6033        rust_language.clone(),
 6034        &mut cx,
 6035    );
 6036
 6037    // Test that different comment prefix characters (e.g., '#') are handled correctly
 6038    assert_rewrap(
 6039        indoc! {"
 6040            # ˇThis is a long comment using a pound sign.
 6041        "},
 6042        indoc! {"
 6043            # ˇThis is a long comment using a pound
 6044            # sign.
 6045        "},
 6046        python_language,
 6047        &mut cx,
 6048    );
 6049
 6050    // Test rewrapping only affects comments, not code even when selected
 6051    assert_rewrap(
 6052        indoc! {"
 6053            «/// This doc comment is long and should be wrapped.
 6054            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6055        "},
 6056        indoc! {"
 6057            «/// This doc comment is long and should
 6058            /// be wrapped.
 6059            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6060        "},
 6061        rust_language.clone(),
 6062        &mut cx,
 6063    );
 6064
 6065    // Test that rewrapping works in Markdown documents where `allow_rewrap` is `Anywhere`
 6066    assert_rewrap(
 6067        indoc! {"
 6068            # Header
 6069
 6070            A long long long line of markdown text to wrap.ˇ
 6071         "},
 6072        indoc! {"
 6073            # Header
 6074
 6075            A long long long line of markdown text
 6076            to wrap.ˇ
 6077         "},
 6078        markdown_language.clone(),
 6079        &mut cx,
 6080    );
 6081
 6082    // Test that rewrapping boundary works and preserves relative indent for Markdown documents
 6083    assert_rewrap(
 6084        indoc! {"
 6085            «1. This is a numbered list item that is very long and needs to be wrapped properly.
 6086            2. This is a numbered list item that is very long and needs to be wrapped properly.
 6087            - This is an unordered list item that is also very long and should not merge with the numbered item.ˇ»
 6088        "},
 6089        indoc! {"
 6090            «1. This is a numbered list item that is
 6091               very long and needs to be wrapped
 6092               properly.
 6093            2. This is a numbered list item that is
 6094               very long and needs to be wrapped
 6095               properly.
 6096            - This is an unordered list item that is
 6097              also very long and should not merge
 6098              with the numbered item.ˇ»
 6099        "},
 6100        markdown_language.clone(),
 6101        &mut cx,
 6102    );
 6103
 6104    // Test that rewrapping add indents for rewrapping boundary if not exists already.
 6105    assert_rewrap(
 6106        indoc! {"
 6107            «1. This is a numbered list item that is
 6108            very long and needs to be wrapped
 6109            properly.
 6110            2. This is a numbered list item that is
 6111            very long and needs to be wrapped
 6112            properly.
 6113            - This is an unordered list item that is
 6114            also very long and should not merge with
 6115            the numbered item.ˇ»
 6116        "},
 6117        indoc! {"
 6118            «1. This is a numbered list item that is
 6119               very long and needs to be wrapped
 6120               properly.
 6121            2. This is a numbered list item that is
 6122               very long and needs to be wrapped
 6123               properly.
 6124            - This is an unordered list item that is
 6125              also very long and should not merge
 6126              with the numbered item.ˇ»
 6127        "},
 6128        markdown_language.clone(),
 6129        &mut cx,
 6130    );
 6131
 6132    // Test that rewrapping maintain indents even when they already exists.
 6133    assert_rewrap(
 6134        indoc! {"
 6135            «1. This is a numbered list
 6136               item that is very long and needs to be wrapped properly.
 6137            2. This is a numbered list
 6138               item that is very long and needs to be wrapped properly.
 6139            - This is an unordered list item that is also very long and
 6140              should not merge with the numbered item.ˇ»
 6141        "},
 6142        indoc! {"
 6143            «1. This is a numbered list item that is
 6144               very long and needs to be wrapped
 6145               properly.
 6146            2. This is a numbered list item that is
 6147               very long and needs to be wrapped
 6148               properly.
 6149            - This is an unordered list item that is
 6150              also very long and should not merge
 6151              with the numbered item.ˇ»
 6152        "},
 6153        markdown_language,
 6154        &mut cx,
 6155    );
 6156
 6157    // Test that rewrapping works in plain text where `allow_rewrap` is `Anywhere`
 6158    assert_rewrap(
 6159        indoc! {"
 6160            ˇThis is a very long line of plain text that will be wrapped.
 6161        "},
 6162        indoc! {"
 6163            ˇThis is a very long line of plain text
 6164            that will be wrapped.
 6165        "},
 6166        plaintext_language.clone(),
 6167        &mut cx,
 6168    );
 6169
 6170    // Test that non-commented code acts as a paragraph boundary within a selection
 6171    assert_rewrap(
 6172        indoc! {"
 6173               «// This is the first long comment block to be wrapped.
 6174               fn my_func(a: u32);
 6175               // This is the second long comment block to be wrapped.ˇ»
 6176           "},
 6177        indoc! {"
 6178               «// This is the first long comment block
 6179               // to be wrapped.
 6180               fn my_func(a: u32);
 6181               // This is the second long comment block
 6182               // to be wrapped.ˇ»
 6183           "},
 6184        rust_language,
 6185        &mut cx,
 6186    );
 6187
 6188    // Test rewrapping multiple selections, including ones with blank lines or tabs
 6189    assert_rewrap(
 6190        indoc! {"
 6191            «ˇThis is a very long line that will be wrapped.
 6192
 6193            This is another paragraph in the same selection.»
 6194
 6195            «\tThis is a very long indented line that will be wrapped.ˇ»
 6196         "},
 6197        indoc! {"
 6198            «ˇThis is a very long line that will be
 6199            wrapped.
 6200
 6201            This is another paragraph in the same
 6202            selection.»
 6203
 6204            «\tThis is a very long indented line
 6205            \tthat will be wrapped.ˇ»
 6206         "},
 6207        plaintext_language,
 6208        &mut cx,
 6209    );
 6210
 6211    // Test that an empty comment line acts as a paragraph boundary
 6212    assert_rewrap(
 6213        indoc! {"
 6214            // ˇThis is a long comment that will be wrapped.
 6215            //
 6216            // And this is another long comment that will also be wrapped.ˇ
 6217         "},
 6218        indoc! {"
 6219            // ˇThis is a long comment that will be
 6220            // wrapped.
 6221            //
 6222            // And this is another long comment that
 6223            // will also be wrapped.ˇ
 6224         "},
 6225        cpp_language,
 6226        &mut cx,
 6227    );
 6228
 6229    #[track_caller]
 6230    fn assert_rewrap(
 6231        unwrapped_text: &str,
 6232        wrapped_text: &str,
 6233        language: Arc<Language>,
 6234        cx: &mut EditorTestContext,
 6235    ) {
 6236        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6237        cx.set_state(unwrapped_text);
 6238        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6239        cx.assert_editor_state(wrapped_text);
 6240    }
 6241}
 6242
 6243#[gpui::test]
 6244async fn test_rewrap_block_comments(cx: &mut TestAppContext) {
 6245    init_test(cx, |settings| {
 6246        settings.languages.0.extend([(
 6247            "Rust".into(),
 6248            LanguageSettingsContent {
 6249                allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6250                preferred_line_length: Some(40),
 6251                ..Default::default()
 6252            },
 6253        )])
 6254    });
 6255
 6256    let mut cx = EditorTestContext::new(cx).await;
 6257
 6258    let rust_lang = Arc::new(
 6259        Language::new(
 6260            LanguageConfig {
 6261                name: "Rust".into(),
 6262                line_comments: vec!["// ".into()],
 6263                block_comment: Some(BlockCommentConfig {
 6264                    start: "/*".into(),
 6265                    end: "*/".into(),
 6266                    prefix: "* ".into(),
 6267                    tab_size: 1,
 6268                }),
 6269                documentation_comment: Some(BlockCommentConfig {
 6270                    start: "/**".into(),
 6271                    end: "*/".into(),
 6272                    prefix: "* ".into(),
 6273                    tab_size: 1,
 6274                }),
 6275
 6276                ..LanguageConfig::default()
 6277            },
 6278            Some(tree_sitter_rust::LANGUAGE.into()),
 6279        )
 6280        .with_override_query("[(line_comment) (block_comment)] @comment.inclusive")
 6281        .unwrap(),
 6282    );
 6283
 6284    // regular block comment
 6285    assert_rewrap(
 6286        indoc! {"
 6287            /*
 6288             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6289             */
 6290            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6291        "},
 6292        indoc! {"
 6293            /*
 6294             *ˇ Lorem ipsum dolor sit amet,
 6295             * consectetur adipiscing elit.
 6296             */
 6297            /*
 6298             *ˇ Lorem ipsum dolor sit amet,
 6299             * consectetur adipiscing elit.
 6300             */
 6301        "},
 6302        rust_lang.clone(),
 6303        &mut cx,
 6304    );
 6305
 6306    // indent is respected
 6307    assert_rewrap(
 6308        indoc! {"
 6309            {}
 6310                /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6311        "},
 6312        indoc! {"
 6313            {}
 6314                /*
 6315                 *ˇ Lorem ipsum dolor sit amet,
 6316                 * consectetur adipiscing elit.
 6317                 */
 6318        "},
 6319        rust_lang.clone(),
 6320        &mut cx,
 6321    );
 6322
 6323    // short block comments with inline delimiters
 6324    assert_rewrap(
 6325        indoc! {"
 6326            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6327            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6328             */
 6329            /*
 6330             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6331        "},
 6332        indoc! {"
 6333            /*
 6334             *ˇ Lorem ipsum dolor sit amet,
 6335             * consectetur adipiscing elit.
 6336             */
 6337            /*
 6338             *ˇ Lorem ipsum dolor sit amet,
 6339             * consectetur adipiscing elit.
 6340             */
 6341            /*
 6342             *ˇ Lorem ipsum dolor sit amet,
 6343             * consectetur adipiscing elit.
 6344             */
 6345        "},
 6346        rust_lang.clone(),
 6347        &mut cx,
 6348    );
 6349
 6350    // multiline block comment with inline start/end delimiters
 6351    assert_rewrap(
 6352        indoc! {"
 6353            /*ˇ Lorem ipsum dolor sit amet,
 6354             * consectetur adipiscing elit. */
 6355        "},
 6356        indoc! {"
 6357            /*
 6358             *ˇ Lorem ipsum dolor sit amet,
 6359             * consectetur adipiscing elit.
 6360             */
 6361        "},
 6362        rust_lang.clone(),
 6363        &mut cx,
 6364    );
 6365
 6366    // block comment rewrap still respects paragraph bounds
 6367    assert_rewrap(
 6368        indoc! {"
 6369            /*
 6370             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6371             *
 6372             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6373             */
 6374        "},
 6375        indoc! {"
 6376            /*
 6377             *ˇ Lorem ipsum dolor sit amet,
 6378             * consectetur adipiscing elit.
 6379             *
 6380             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6381             */
 6382        "},
 6383        rust_lang.clone(),
 6384        &mut cx,
 6385    );
 6386
 6387    // documentation comments
 6388    assert_rewrap(
 6389        indoc! {"
 6390            /**ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6391            /**
 6392             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6393             */
 6394        "},
 6395        indoc! {"
 6396            /**
 6397             *ˇ Lorem ipsum dolor sit amet,
 6398             * consectetur adipiscing elit.
 6399             */
 6400            /**
 6401             *ˇ Lorem ipsum dolor sit amet,
 6402             * consectetur adipiscing elit.
 6403             */
 6404        "},
 6405        rust_lang.clone(),
 6406        &mut cx,
 6407    );
 6408
 6409    // different, adjacent comments
 6410    assert_rewrap(
 6411        indoc! {"
 6412            /**
 6413             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6414             */
 6415            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6416            //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6417        "},
 6418        indoc! {"
 6419            /**
 6420             *ˇ Lorem ipsum dolor sit amet,
 6421             * consectetur adipiscing elit.
 6422             */
 6423            /*
 6424             *ˇ Lorem ipsum dolor sit amet,
 6425             * consectetur adipiscing elit.
 6426             */
 6427            //ˇ Lorem ipsum dolor sit amet,
 6428            // consectetur adipiscing elit.
 6429        "},
 6430        rust_lang.clone(),
 6431        &mut cx,
 6432    );
 6433
 6434    // selection w/ single short block comment
 6435    assert_rewrap(
 6436        indoc! {"
 6437            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6438        "},
 6439        indoc! {"
 6440            «/*
 6441             * Lorem ipsum dolor sit amet,
 6442             * consectetur adipiscing elit.
 6443             */ˇ»
 6444        "},
 6445        rust_lang.clone(),
 6446        &mut cx,
 6447    );
 6448
 6449    // rewrapping a single comment w/ abutting comments
 6450    assert_rewrap(
 6451        indoc! {"
 6452            /* ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6453            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6454        "},
 6455        indoc! {"
 6456            /*
 6457             * ˇLorem ipsum dolor sit amet,
 6458             * consectetur adipiscing elit.
 6459             */
 6460            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6461        "},
 6462        rust_lang.clone(),
 6463        &mut cx,
 6464    );
 6465
 6466    // selection w/ non-abutting short block comments
 6467    assert_rewrap(
 6468        indoc! {"
 6469            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6470
 6471            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6472        "},
 6473        indoc! {"
 6474            «/*
 6475             * Lorem ipsum dolor sit amet,
 6476             * consectetur adipiscing elit.
 6477             */
 6478
 6479            /*
 6480             * Lorem ipsum dolor sit amet,
 6481             * consectetur adipiscing elit.
 6482             */ˇ»
 6483        "},
 6484        rust_lang.clone(),
 6485        &mut cx,
 6486    );
 6487
 6488    // selection of multiline block comments
 6489    assert_rewrap(
 6490        indoc! {"
 6491            «/* Lorem ipsum dolor sit amet,
 6492             * consectetur adipiscing elit. */ˇ»
 6493        "},
 6494        indoc! {"
 6495            «/*
 6496             * Lorem ipsum dolor sit amet,
 6497             * consectetur adipiscing elit.
 6498             */ˇ»
 6499        "},
 6500        rust_lang.clone(),
 6501        &mut cx,
 6502    );
 6503
 6504    // partial selection of multiline block comments
 6505    assert_rewrap(
 6506        indoc! {"
 6507            «/* Lorem ipsum dolor sit amet,ˇ»
 6508             * consectetur adipiscing elit. */
 6509            /* Lorem ipsum dolor sit amet,
 6510             «* consectetur adipiscing elit. */ˇ»
 6511        "},
 6512        indoc! {"
 6513            «/*
 6514             * Lorem ipsum dolor sit amet,ˇ»
 6515             * consectetur adipiscing elit. */
 6516            /* Lorem ipsum dolor sit amet,
 6517             «* consectetur adipiscing elit.
 6518             */ˇ»
 6519        "},
 6520        rust_lang.clone(),
 6521        &mut cx,
 6522    );
 6523
 6524    // selection w/ abutting short block comments
 6525    // TODO: should not be combined; should rewrap as 2 comments
 6526    assert_rewrap(
 6527        indoc! {"
 6528            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6529            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6530        "},
 6531        // desired behavior:
 6532        // indoc! {"
 6533        //     «/*
 6534        //      * Lorem ipsum dolor sit amet,
 6535        //      * consectetur adipiscing elit.
 6536        //      */
 6537        //     /*
 6538        //      * Lorem ipsum dolor sit amet,
 6539        //      * consectetur adipiscing elit.
 6540        //      */ˇ»
 6541        // "},
 6542        // actual behaviour:
 6543        indoc! {"
 6544            «/*
 6545             * Lorem ipsum dolor sit amet,
 6546             * consectetur adipiscing elit. Lorem
 6547             * ipsum dolor sit amet, consectetur
 6548             * adipiscing elit.
 6549             */ˇ»
 6550        "},
 6551        rust_lang.clone(),
 6552        &mut cx,
 6553    );
 6554
 6555    // TODO: same as above, but with delimiters on separate line
 6556    // assert_rewrap(
 6557    //     indoc! {"
 6558    //         «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6559    //          */
 6560    //         /*
 6561    //          * Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6562    //     "},
 6563    //     // desired:
 6564    //     // indoc! {"
 6565    //     //     «/*
 6566    //     //      * Lorem ipsum dolor sit amet,
 6567    //     //      * consectetur adipiscing elit.
 6568    //     //      */
 6569    //     //     /*
 6570    //     //      * Lorem ipsum dolor sit amet,
 6571    //     //      * consectetur adipiscing elit.
 6572    //     //      */ˇ»
 6573    //     // "},
 6574    //     // actual: (but with trailing w/s on the empty lines)
 6575    //     indoc! {"
 6576    //         «/*
 6577    //          * Lorem ipsum dolor sit amet,
 6578    //          * consectetur adipiscing elit.
 6579    //          *
 6580    //          */
 6581    //         /*
 6582    //          *
 6583    //          * Lorem ipsum dolor sit amet,
 6584    //          * consectetur adipiscing elit.
 6585    //          */ˇ»
 6586    //     "},
 6587    //     rust_lang.clone(),
 6588    //     &mut cx,
 6589    // );
 6590
 6591    // TODO these are unhandled edge cases; not correct, just documenting known issues
 6592    assert_rewrap(
 6593        indoc! {"
 6594            /*
 6595             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6596             */
 6597            /*
 6598             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6599            /*ˇ Lorem ipsum dolor sit amet */ /* consectetur adipiscing elit. */
 6600        "},
 6601        // desired:
 6602        // indoc! {"
 6603        //     /*
 6604        //      *ˇ Lorem ipsum dolor sit amet,
 6605        //      * consectetur adipiscing elit.
 6606        //      */
 6607        //     /*
 6608        //      *ˇ Lorem ipsum dolor sit amet,
 6609        //      * consectetur adipiscing elit.
 6610        //      */
 6611        //     /*
 6612        //      *ˇ Lorem ipsum dolor sit amet
 6613        //      */ /* consectetur adipiscing elit. */
 6614        // "},
 6615        // actual:
 6616        indoc! {"
 6617            /*
 6618             //ˇ Lorem ipsum dolor sit amet,
 6619             // consectetur adipiscing elit.
 6620             */
 6621            /*
 6622             * //ˇ Lorem ipsum dolor sit amet,
 6623             * consectetur adipiscing elit.
 6624             */
 6625            /*
 6626             *ˇ Lorem ipsum dolor sit amet */ /*
 6627             * consectetur adipiscing elit.
 6628             */
 6629        "},
 6630        rust_lang,
 6631        &mut cx,
 6632    );
 6633
 6634    #[track_caller]
 6635    fn assert_rewrap(
 6636        unwrapped_text: &str,
 6637        wrapped_text: &str,
 6638        language: Arc<Language>,
 6639        cx: &mut EditorTestContext,
 6640    ) {
 6641        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6642        cx.set_state(unwrapped_text);
 6643        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6644        cx.assert_editor_state(wrapped_text);
 6645    }
 6646}
 6647
 6648#[gpui::test]
 6649async fn test_hard_wrap(cx: &mut TestAppContext) {
 6650    init_test(cx, |_| {});
 6651    let mut cx = EditorTestContext::new(cx).await;
 6652
 6653    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 6654    cx.update_editor(|editor, _, cx| {
 6655        editor.set_hard_wrap(Some(14), cx);
 6656    });
 6657
 6658    cx.set_state(indoc!(
 6659        "
 6660        one two three ˇ
 6661        "
 6662    ));
 6663    cx.simulate_input("four");
 6664    cx.run_until_parked();
 6665
 6666    cx.assert_editor_state(indoc!(
 6667        "
 6668        one two three
 6669        fourˇ
 6670        "
 6671    ));
 6672
 6673    cx.update_editor(|editor, window, cx| {
 6674        editor.newline(&Default::default(), window, cx);
 6675    });
 6676    cx.run_until_parked();
 6677    cx.assert_editor_state(indoc!(
 6678        "
 6679        one two three
 6680        four
 6681        ˇ
 6682        "
 6683    ));
 6684
 6685    cx.simulate_input("five");
 6686    cx.run_until_parked();
 6687    cx.assert_editor_state(indoc!(
 6688        "
 6689        one two three
 6690        four
 6691        fiveˇ
 6692        "
 6693    ));
 6694
 6695    cx.update_editor(|editor, window, cx| {
 6696        editor.newline(&Default::default(), window, cx);
 6697    });
 6698    cx.run_until_parked();
 6699    cx.simulate_input("# ");
 6700    cx.run_until_parked();
 6701    cx.assert_editor_state(indoc!(
 6702        "
 6703        one two three
 6704        four
 6705        five
 6706        # ˇ
 6707        "
 6708    ));
 6709
 6710    cx.update_editor(|editor, window, cx| {
 6711        editor.newline(&Default::default(), window, cx);
 6712    });
 6713    cx.run_until_parked();
 6714    cx.assert_editor_state(indoc!(
 6715        "
 6716        one two three
 6717        four
 6718        five
 6719        #\x20
 6720 6721        "
 6722    ));
 6723
 6724    cx.simulate_input(" 6");
 6725    cx.run_until_parked();
 6726    cx.assert_editor_state(indoc!(
 6727        "
 6728        one two three
 6729        four
 6730        five
 6731        #
 6732        # 6ˇ
 6733        "
 6734    ));
 6735}
 6736
 6737#[gpui::test]
 6738async fn test_cut_line_ends(cx: &mut TestAppContext) {
 6739    init_test(cx, |_| {});
 6740
 6741    let mut cx = EditorTestContext::new(cx).await;
 6742
 6743    cx.set_state(indoc! {"
 6744        The quick« brownˇ»
 6745        fox jumps overˇ
 6746        the lazy dog"});
 6747    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6748    cx.assert_editor_state(indoc! {"
 6749        The quickˇ
 6750        ˇthe lazy dog"});
 6751
 6752    cx.set_state(indoc! {"
 6753        The quick« brownˇ»
 6754        fox jumps overˇ
 6755        the lazy dog"});
 6756    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 6757    cx.assert_editor_state(indoc! {"
 6758        The quickˇ
 6759        fox jumps overˇthe lazy dog"});
 6760
 6761    cx.set_state(indoc! {"
 6762        The quick« brownˇ»
 6763        fox jumps overˇ
 6764        the lazy dog"});
 6765    cx.update_editor(|e, window, cx| {
 6766        e.cut_to_end_of_line(
 6767            &CutToEndOfLine {
 6768                stop_at_newlines: true,
 6769            },
 6770            window,
 6771            cx,
 6772        )
 6773    });
 6774    cx.assert_editor_state(indoc! {"
 6775        The quickˇ
 6776        fox jumps overˇ
 6777        the lazy dog"});
 6778
 6779    cx.set_state(indoc! {"
 6780        The quick« brownˇ»
 6781        fox jumps overˇ
 6782        the lazy dog"});
 6783    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 6784    cx.assert_editor_state(indoc! {"
 6785        The quickˇ
 6786        fox jumps overˇthe lazy dog"});
 6787}
 6788
 6789#[gpui::test]
 6790async fn test_clipboard(cx: &mut TestAppContext) {
 6791    init_test(cx, |_| {});
 6792
 6793    let mut cx = EditorTestContext::new(cx).await;
 6794
 6795    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 6796    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6797    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 6798
 6799    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 6800    cx.set_state("two ˇfour ˇsix ˇ");
 6801    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6802    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 6803
 6804    // Paste again but with only two cursors. Since the number of cursors doesn't
 6805    // match the number of slices in the clipboard, the entire clipboard text
 6806    // is pasted at each cursor.
 6807    cx.set_state("ˇtwo one✅ four three six five ˇ");
 6808    cx.update_editor(|e, window, cx| {
 6809        e.handle_input("( ", window, cx);
 6810        e.paste(&Paste, window, cx);
 6811        e.handle_input(") ", window, cx);
 6812    });
 6813    cx.assert_editor_state(
 6814        &([
 6815            "( one✅ ",
 6816            "three ",
 6817            "five ) ˇtwo one✅ four three six five ( one✅ ",
 6818            "three ",
 6819            "five ) ˇ",
 6820        ]
 6821        .join("\n")),
 6822    );
 6823
 6824    // Cut with three selections, one of which is full-line.
 6825    cx.set_state(indoc! {"
 6826        1«2ˇ»3
 6827        4ˇ567
 6828        «8ˇ»9"});
 6829    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6830    cx.assert_editor_state(indoc! {"
 6831        1ˇ3
 6832        ˇ9"});
 6833
 6834    // Paste with three selections, noticing how the copied selection that was full-line
 6835    // gets inserted before the second cursor.
 6836    cx.set_state(indoc! {"
 6837        1ˇ3
 6838 6839        «oˇ»ne"});
 6840    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6841    cx.assert_editor_state(indoc! {"
 6842        12ˇ3
 6843        4567
 6844 6845        8ˇne"});
 6846
 6847    // Copy with a single cursor only, which writes the whole line into the clipboard.
 6848    cx.set_state(indoc! {"
 6849        The quick brown
 6850        fox juˇmps over
 6851        the lazy dog"});
 6852    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6853    assert_eq!(
 6854        cx.read_from_clipboard()
 6855            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6856        Some("fox jumps over\n".to_string())
 6857    );
 6858
 6859    // Paste with three selections, noticing how the copied full-line selection is inserted
 6860    // before the empty selections but replaces the selection that is non-empty.
 6861    cx.set_state(indoc! {"
 6862        Tˇhe quick brown
 6863        «foˇ»x jumps over
 6864        tˇhe lazy dog"});
 6865    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6866    cx.assert_editor_state(indoc! {"
 6867        fox jumps over
 6868        Tˇhe quick brown
 6869        fox jumps over
 6870        ˇx jumps over
 6871        fox jumps over
 6872        tˇhe lazy dog"});
 6873}
 6874
 6875#[gpui::test]
 6876async fn test_copy_trim(cx: &mut TestAppContext) {
 6877    init_test(cx, |_| {});
 6878
 6879    let mut cx = EditorTestContext::new(cx).await;
 6880    cx.set_state(
 6881        r#"            «for selection in selections.iter() {
 6882            let mut start = selection.start;
 6883            let mut end = selection.end;
 6884            let is_entire_line = selection.is_empty();
 6885            if is_entire_line {
 6886                start = Point::new(start.row, 0);ˇ»
 6887                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6888            }
 6889        "#,
 6890    );
 6891    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6892    assert_eq!(
 6893        cx.read_from_clipboard()
 6894            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6895        Some(
 6896            "for selection in selections.iter() {
 6897            let mut start = selection.start;
 6898            let mut end = selection.end;
 6899            let is_entire_line = selection.is_empty();
 6900            if is_entire_line {
 6901                start = Point::new(start.row, 0);"
 6902                .to_string()
 6903        ),
 6904        "Regular copying preserves all indentation selected",
 6905    );
 6906    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6907    assert_eq!(
 6908        cx.read_from_clipboard()
 6909            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6910        Some(
 6911            "for selection in selections.iter() {
 6912let mut start = selection.start;
 6913let mut end = selection.end;
 6914let is_entire_line = selection.is_empty();
 6915if is_entire_line {
 6916    start = Point::new(start.row, 0);"
 6917                .to_string()
 6918        ),
 6919        "Copying with stripping should strip all leading whitespaces"
 6920    );
 6921
 6922    cx.set_state(
 6923        r#"       «     for selection in selections.iter() {
 6924            let mut start = selection.start;
 6925            let mut end = selection.end;
 6926            let is_entire_line = selection.is_empty();
 6927            if is_entire_line {
 6928                start = Point::new(start.row, 0);ˇ»
 6929                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6930            }
 6931        "#,
 6932    );
 6933    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6934    assert_eq!(
 6935        cx.read_from_clipboard()
 6936            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6937        Some(
 6938            "     for selection in selections.iter() {
 6939            let mut start = selection.start;
 6940            let mut end = selection.end;
 6941            let is_entire_line = selection.is_empty();
 6942            if is_entire_line {
 6943                start = Point::new(start.row, 0);"
 6944                .to_string()
 6945        ),
 6946        "Regular copying preserves all indentation selected",
 6947    );
 6948    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6949    assert_eq!(
 6950        cx.read_from_clipboard()
 6951            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6952        Some(
 6953            "for selection in selections.iter() {
 6954let mut start = selection.start;
 6955let mut end = selection.end;
 6956let is_entire_line = selection.is_empty();
 6957if is_entire_line {
 6958    start = Point::new(start.row, 0);"
 6959                .to_string()
 6960        ),
 6961        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 6962    );
 6963
 6964    cx.set_state(
 6965        r#"       «ˇ     for selection in selections.iter() {
 6966            let mut start = selection.start;
 6967            let mut end = selection.end;
 6968            let is_entire_line = selection.is_empty();
 6969            if is_entire_line {
 6970                start = Point::new(start.row, 0);»
 6971                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6972            }
 6973        "#,
 6974    );
 6975    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6976    assert_eq!(
 6977        cx.read_from_clipboard()
 6978            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6979        Some(
 6980            "     for selection in selections.iter() {
 6981            let mut start = selection.start;
 6982            let mut end = selection.end;
 6983            let is_entire_line = selection.is_empty();
 6984            if is_entire_line {
 6985                start = Point::new(start.row, 0);"
 6986                .to_string()
 6987        ),
 6988        "Regular copying for reverse selection works the same",
 6989    );
 6990    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6991    assert_eq!(
 6992        cx.read_from_clipboard()
 6993            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6994        Some(
 6995            "for selection in selections.iter() {
 6996let mut start = selection.start;
 6997let mut end = selection.end;
 6998let is_entire_line = selection.is_empty();
 6999if is_entire_line {
 7000    start = Point::new(start.row, 0);"
 7001                .to_string()
 7002        ),
 7003        "Copying with stripping for reverse selection works the same"
 7004    );
 7005
 7006    cx.set_state(
 7007        r#"            for selection «in selections.iter() {
 7008            let mut start = selection.start;
 7009            let mut end = selection.end;
 7010            let is_entire_line = selection.is_empty();
 7011            if is_entire_line {
 7012                start = Point::new(start.row, 0);ˇ»
 7013                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7014            }
 7015        "#,
 7016    );
 7017    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7018    assert_eq!(
 7019        cx.read_from_clipboard()
 7020            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7021        Some(
 7022            "in selections.iter() {
 7023            let mut start = selection.start;
 7024            let mut end = selection.end;
 7025            let is_entire_line = selection.is_empty();
 7026            if is_entire_line {
 7027                start = Point::new(start.row, 0);"
 7028                .to_string()
 7029        ),
 7030        "When selecting past the indent, the copying works as usual",
 7031    );
 7032    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7033    assert_eq!(
 7034        cx.read_from_clipboard()
 7035            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7036        Some(
 7037            "in selections.iter() {
 7038            let mut start = selection.start;
 7039            let mut end = selection.end;
 7040            let is_entire_line = selection.is_empty();
 7041            if is_entire_line {
 7042                start = Point::new(start.row, 0);"
 7043                .to_string()
 7044        ),
 7045        "When selecting past the indent, nothing is trimmed"
 7046    );
 7047
 7048    cx.set_state(
 7049        r#"            «for selection in selections.iter() {
 7050            let mut start = selection.start;
 7051
 7052            let mut end = selection.end;
 7053            let is_entire_line = selection.is_empty();
 7054            if is_entire_line {
 7055                start = Point::new(start.row, 0);
 7056ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7057            }
 7058        "#,
 7059    );
 7060    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7061    assert_eq!(
 7062        cx.read_from_clipboard()
 7063            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7064        Some(
 7065            "for selection in selections.iter() {
 7066let mut start = selection.start;
 7067
 7068let mut end = selection.end;
 7069let is_entire_line = selection.is_empty();
 7070if is_entire_line {
 7071    start = Point::new(start.row, 0);
 7072"
 7073            .to_string()
 7074        ),
 7075        "Copying with stripping should ignore empty lines"
 7076    );
 7077}
 7078
 7079#[gpui::test]
 7080async fn test_paste_multiline(cx: &mut TestAppContext) {
 7081    init_test(cx, |_| {});
 7082
 7083    let mut cx = EditorTestContext::new(cx).await;
 7084    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7085
 7086    // Cut an indented block, without the leading whitespace.
 7087    cx.set_state(indoc! {"
 7088        const a: B = (
 7089            c(),
 7090            «d(
 7091                e,
 7092                f
 7093            )ˇ»
 7094        );
 7095    "});
 7096    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7097    cx.assert_editor_state(indoc! {"
 7098        const a: B = (
 7099            c(),
 7100            ˇ
 7101        );
 7102    "});
 7103
 7104    // Paste it at the same position.
 7105    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7106    cx.assert_editor_state(indoc! {"
 7107        const a: B = (
 7108            c(),
 7109            d(
 7110                e,
 7111                f
 7112 7113        );
 7114    "});
 7115
 7116    // Paste it at a line with a lower indent level.
 7117    cx.set_state(indoc! {"
 7118        ˇ
 7119        const a: B = (
 7120            c(),
 7121        );
 7122    "});
 7123    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7124    cx.assert_editor_state(indoc! {"
 7125        d(
 7126            e,
 7127            f
 7128 7129        const a: B = (
 7130            c(),
 7131        );
 7132    "});
 7133
 7134    // Cut an indented block, with the leading whitespace.
 7135    cx.set_state(indoc! {"
 7136        const a: B = (
 7137            c(),
 7138        «    d(
 7139                e,
 7140                f
 7141            )
 7142        ˇ»);
 7143    "});
 7144    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7145    cx.assert_editor_state(indoc! {"
 7146        const a: B = (
 7147            c(),
 7148        ˇ);
 7149    "});
 7150
 7151    // Paste it at the same position.
 7152    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7153    cx.assert_editor_state(indoc! {"
 7154        const a: B = (
 7155            c(),
 7156            d(
 7157                e,
 7158                f
 7159            )
 7160        ˇ);
 7161    "});
 7162
 7163    // Paste it at a line with a higher indent level.
 7164    cx.set_state(indoc! {"
 7165        const a: B = (
 7166            c(),
 7167            d(
 7168                e,
 7169 7170            )
 7171        );
 7172    "});
 7173    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7174    cx.assert_editor_state(indoc! {"
 7175        const a: B = (
 7176            c(),
 7177            d(
 7178                e,
 7179                f    d(
 7180                    e,
 7181                    f
 7182                )
 7183        ˇ
 7184            )
 7185        );
 7186    "});
 7187
 7188    // Copy an indented block, starting mid-line
 7189    cx.set_state(indoc! {"
 7190        const a: B = (
 7191            c(),
 7192            somethin«g(
 7193                e,
 7194                f
 7195            )ˇ»
 7196        );
 7197    "});
 7198    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7199
 7200    // Paste it on a line with a lower indent level
 7201    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 7202    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7203    cx.assert_editor_state(indoc! {"
 7204        const a: B = (
 7205            c(),
 7206            something(
 7207                e,
 7208                f
 7209            )
 7210        );
 7211        g(
 7212            e,
 7213            f
 7214"});
 7215}
 7216
 7217#[gpui::test]
 7218async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 7219    init_test(cx, |_| {});
 7220
 7221    cx.write_to_clipboard(ClipboardItem::new_string(
 7222        "    d(\n        e\n    );\n".into(),
 7223    ));
 7224
 7225    let mut cx = EditorTestContext::new(cx).await;
 7226    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7227
 7228    cx.set_state(indoc! {"
 7229        fn a() {
 7230            b();
 7231            if c() {
 7232                ˇ
 7233            }
 7234        }
 7235    "});
 7236
 7237    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7238    cx.assert_editor_state(indoc! {"
 7239        fn a() {
 7240            b();
 7241            if c() {
 7242                d(
 7243                    e
 7244                );
 7245        ˇ
 7246            }
 7247        }
 7248    "});
 7249
 7250    cx.set_state(indoc! {"
 7251        fn a() {
 7252            b();
 7253            ˇ
 7254        }
 7255    "});
 7256
 7257    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7258    cx.assert_editor_state(indoc! {"
 7259        fn a() {
 7260            b();
 7261            d(
 7262                e
 7263            );
 7264        ˇ
 7265        }
 7266    "});
 7267}
 7268
 7269#[gpui::test]
 7270fn test_select_all(cx: &mut TestAppContext) {
 7271    init_test(cx, |_| {});
 7272
 7273    let editor = cx.add_window(|window, cx| {
 7274        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 7275        build_editor(buffer, window, cx)
 7276    });
 7277    _ = editor.update(cx, |editor, window, cx| {
 7278        editor.select_all(&SelectAll, window, cx);
 7279        assert_eq!(
 7280            editor.selections.display_ranges(cx),
 7281            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 7282        );
 7283    });
 7284}
 7285
 7286#[gpui::test]
 7287fn test_select_line(cx: &mut TestAppContext) {
 7288    init_test(cx, |_| {});
 7289
 7290    let editor = cx.add_window(|window, cx| {
 7291        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 7292        build_editor(buffer, window, cx)
 7293    });
 7294    _ = editor.update(cx, |editor, window, cx| {
 7295        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7296            s.select_display_ranges([
 7297                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7298                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7299                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7300                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 7301            ])
 7302        });
 7303        editor.select_line(&SelectLine, window, cx);
 7304        assert_eq!(
 7305            editor.selections.display_ranges(cx),
 7306            vec![
 7307                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 7308                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 7309            ]
 7310        );
 7311    });
 7312
 7313    _ = editor.update(cx, |editor, window, cx| {
 7314        editor.select_line(&SelectLine, window, cx);
 7315        assert_eq!(
 7316            editor.selections.display_ranges(cx),
 7317            vec![
 7318                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 7319                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 7320            ]
 7321        );
 7322    });
 7323
 7324    _ = editor.update(cx, |editor, window, cx| {
 7325        editor.select_line(&SelectLine, window, cx);
 7326        assert_eq!(
 7327            editor.selections.display_ranges(cx),
 7328            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 7329        );
 7330    });
 7331}
 7332
 7333#[gpui::test]
 7334async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 7335    init_test(cx, |_| {});
 7336    let mut cx = EditorTestContext::new(cx).await;
 7337
 7338    #[track_caller]
 7339    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 7340        cx.set_state(initial_state);
 7341        cx.update_editor(|e, window, cx| {
 7342            e.split_selection_into_lines(&Default::default(), window, cx)
 7343        });
 7344        cx.assert_editor_state(expected_state);
 7345    }
 7346
 7347    // Selection starts and ends at the middle of lines, left-to-right
 7348    test(
 7349        &mut cx,
 7350        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 7351        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7352    );
 7353    // Same thing, right-to-left
 7354    test(
 7355        &mut cx,
 7356        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 7357        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7358    );
 7359
 7360    // Whole buffer, left-to-right, last line *doesn't* end with newline
 7361    test(
 7362        &mut cx,
 7363        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 7364        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7365    );
 7366    // Same thing, right-to-left
 7367    test(
 7368        &mut cx,
 7369        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 7370        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7371    );
 7372
 7373    // Whole buffer, left-to-right, last line ends with newline
 7374    test(
 7375        &mut cx,
 7376        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 7377        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7378    );
 7379    // Same thing, right-to-left
 7380    test(
 7381        &mut cx,
 7382        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 7383        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7384    );
 7385
 7386    // Starts at the end of a line, ends at the start of another
 7387    test(
 7388        &mut cx,
 7389        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 7390        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 7391    );
 7392}
 7393
 7394#[gpui::test]
 7395async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 7396    init_test(cx, |_| {});
 7397
 7398    let editor = cx.add_window(|window, cx| {
 7399        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 7400        build_editor(buffer, window, cx)
 7401    });
 7402
 7403    // setup
 7404    _ = editor.update(cx, |editor, window, cx| {
 7405        editor.fold_creases(
 7406            vec![
 7407                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 7408                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 7409                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 7410            ],
 7411            true,
 7412            window,
 7413            cx,
 7414        );
 7415        assert_eq!(
 7416            editor.display_text(cx),
 7417            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7418        );
 7419    });
 7420
 7421    _ = editor.update(cx, |editor, window, cx| {
 7422        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7423            s.select_display_ranges([
 7424                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7425                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7426                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7427                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 7428            ])
 7429        });
 7430        editor.split_selection_into_lines(&Default::default(), window, cx);
 7431        assert_eq!(
 7432            editor.display_text(cx),
 7433            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7434        );
 7435    });
 7436    EditorTestContext::for_editor(editor, cx)
 7437        .await
 7438        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 7439
 7440    _ = editor.update(cx, |editor, window, cx| {
 7441        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7442            s.select_display_ranges([
 7443                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 7444            ])
 7445        });
 7446        editor.split_selection_into_lines(&Default::default(), window, cx);
 7447        assert_eq!(
 7448            editor.display_text(cx),
 7449            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 7450        );
 7451        assert_eq!(
 7452            editor.selections.display_ranges(cx),
 7453            [
 7454                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 7455                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 7456                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 7457                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 7458                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 7459                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 7460                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 7461            ]
 7462        );
 7463    });
 7464    EditorTestContext::for_editor(editor, cx)
 7465        .await
 7466        .assert_editor_state(
 7467            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 7468        );
 7469}
 7470
 7471#[gpui::test]
 7472async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 7473    init_test(cx, |_| {});
 7474
 7475    let mut cx = EditorTestContext::new(cx).await;
 7476
 7477    cx.set_state(indoc!(
 7478        r#"abc
 7479           defˇghi
 7480
 7481           jk
 7482           nlmo
 7483           "#
 7484    ));
 7485
 7486    cx.update_editor(|editor, window, cx| {
 7487        editor.add_selection_above(&Default::default(), window, cx);
 7488    });
 7489
 7490    cx.assert_editor_state(indoc!(
 7491        r#"abcˇ
 7492           defˇghi
 7493
 7494           jk
 7495           nlmo
 7496           "#
 7497    ));
 7498
 7499    cx.update_editor(|editor, window, cx| {
 7500        editor.add_selection_above(&Default::default(), window, cx);
 7501    });
 7502
 7503    cx.assert_editor_state(indoc!(
 7504        r#"abcˇ
 7505            defˇghi
 7506
 7507            jk
 7508            nlmo
 7509            "#
 7510    ));
 7511
 7512    cx.update_editor(|editor, window, cx| {
 7513        editor.add_selection_below(&Default::default(), window, cx);
 7514    });
 7515
 7516    cx.assert_editor_state(indoc!(
 7517        r#"abc
 7518           defˇghi
 7519
 7520           jk
 7521           nlmo
 7522           "#
 7523    ));
 7524
 7525    cx.update_editor(|editor, window, cx| {
 7526        editor.undo_selection(&Default::default(), window, cx);
 7527    });
 7528
 7529    cx.assert_editor_state(indoc!(
 7530        r#"abcˇ
 7531           defˇghi
 7532
 7533           jk
 7534           nlmo
 7535           "#
 7536    ));
 7537
 7538    cx.update_editor(|editor, window, cx| {
 7539        editor.redo_selection(&Default::default(), window, cx);
 7540    });
 7541
 7542    cx.assert_editor_state(indoc!(
 7543        r#"abc
 7544           defˇghi
 7545
 7546           jk
 7547           nlmo
 7548           "#
 7549    ));
 7550
 7551    cx.update_editor(|editor, window, cx| {
 7552        editor.add_selection_below(&Default::default(), window, cx);
 7553    });
 7554
 7555    cx.assert_editor_state(indoc!(
 7556        r#"abc
 7557           defˇghi
 7558           ˇ
 7559           jk
 7560           nlmo
 7561           "#
 7562    ));
 7563
 7564    cx.update_editor(|editor, window, cx| {
 7565        editor.add_selection_below(&Default::default(), window, cx);
 7566    });
 7567
 7568    cx.assert_editor_state(indoc!(
 7569        r#"abc
 7570           defˇghi
 7571           ˇ
 7572           jkˇ
 7573           nlmo
 7574           "#
 7575    ));
 7576
 7577    cx.update_editor(|editor, window, cx| {
 7578        editor.add_selection_below(&Default::default(), window, cx);
 7579    });
 7580
 7581    cx.assert_editor_state(indoc!(
 7582        r#"abc
 7583           defˇghi
 7584           ˇ
 7585           jkˇ
 7586           nlmˇo
 7587           "#
 7588    ));
 7589
 7590    cx.update_editor(|editor, window, cx| {
 7591        editor.add_selection_below(&Default::default(), window, cx);
 7592    });
 7593
 7594    cx.assert_editor_state(indoc!(
 7595        r#"abc
 7596           defˇghi
 7597           ˇ
 7598           jkˇ
 7599           nlmˇo
 7600           ˇ"#
 7601    ));
 7602
 7603    // change selections
 7604    cx.set_state(indoc!(
 7605        r#"abc
 7606           def«ˇg»hi
 7607
 7608           jk
 7609           nlmo
 7610           "#
 7611    ));
 7612
 7613    cx.update_editor(|editor, window, cx| {
 7614        editor.add_selection_below(&Default::default(), window, cx);
 7615    });
 7616
 7617    cx.assert_editor_state(indoc!(
 7618        r#"abc
 7619           def«ˇg»hi
 7620
 7621           jk
 7622           nlm«ˇo»
 7623           "#
 7624    ));
 7625
 7626    cx.update_editor(|editor, window, cx| {
 7627        editor.add_selection_below(&Default::default(), window, cx);
 7628    });
 7629
 7630    cx.assert_editor_state(indoc!(
 7631        r#"abc
 7632           def«ˇg»hi
 7633
 7634           jk
 7635           nlm«ˇo»
 7636           "#
 7637    ));
 7638
 7639    cx.update_editor(|editor, window, cx| {
 7640        editor.add_selection_above(&Default::default(), window, cx);
 7641    });
 7642
 7643    cx.assert_editor_state(indoc!(
 7644        r#"abc
 7645           def«ˇg»hi
 7646
 7647           jk
 7648           nlmo
 7649           "#
 7650    ));
 7651
 7652    cx.update_editor(|editor, window, cx| {
 7653        editor.add_selection_above(&Default::default(), window, cx);
 7654    });
 7655
 7656    cx.assert_editor_state(indoc!(
 7657        r#"abc
 7658           def«ˇg»hi
 7659
 7660           jk
 7661           nlmo
 7662           "#
 7663    ));
 7664
 7665    // Change selections again
 7666    cx.set_state(indoc!(
 7667        r#"a«bc
 7668           defgˇ»hi
 7669
 7670           jk
 7671           nlmo
 7672           "#
 7673    ));
 7674
 7675    cx.update_editor(|editor, window, cx| {
 7676        editor.add_selection_below(&Default::default(), window, cx);
 7677    });
 7678
 7679    cx.assert_editor_state(indoc!(
 7680        r#"a«bcˇ»
 7681           d«efgˇ»hi
 7682
 7683           j«kˇ»
 7684           nlmo
 7685           "#
 7686    ));
 7687
 7688    cx.update_editor(|editor, window, cx| {
 7689        editor.add_selection_below(&Default::default(), window, cx);
 7690    });
 7691    cx.assert_editor_state(indoc!(
 7692        r#"a«bcˇ»
 7693           d«efgˇ»hi
 7694
 7695           j«kˇ»
 7696           n«lmoˇ»
 7697           "#
 7698    ));
 7699    cx.update_editor(|editor, window, cx| {
 7700        editor.add_selection_above(&Default::default(), window, cx);
 7701    });
 7702
 7703    cx.assert_editor_state(indoc!(
 7704        r#"a«bcˇ»
 7705           d«efgˇ»hi
 7706
 7707           j«kˇ»
 7708           nlmo
 7709           "#
 7710    ));
 7711
 7712    // Change selections again
 7713    cx.set_state(indoc!(
 7714        r#"abc
 7715           d«ˇefghi
 7716
 7717           jk
 7718           nlm»o
 7719           "#
 7720    ));
 7721
 7722    cx.update_editor(|editor, window, cx| {
 7723        editor.add_selection_above(&Default::default(), window, cx);
 7724    });
 7725
 7726    cx.assert_editor_state(indoc!(
 7727        r#"a«ˇbc»
 7728           d«ˇef»ghi
 7729
 7730           j«ˇk»
 7731           n«ˇlm»o
 7732           "#
 7733    ));
 7734
 7735    cx.update_editor(|editor, window, cx| {
 7736        editor.add_selection_below(&Default::default(), window, cx);
 7737    });
 7738
 7739    cx.assert_editor_state(indoc!(
 7740        r#"abc
 7741           d«ˇef»ghi
 7742
 7743           j«ˇk»
 7744           n«ˇlm»o
 7745           "#
 7746    ));
 7747}
 7748
 7749#[gpui::test]
 7750async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 7751    init_test(cx, |_| {});
 7752    let mut cx = EditorTestContext::new(cx).await;
 7753
 7754    cx.set_state(indoc!(
 7755        r#"line onˇe
 7756           liˇne two
 7757           line three
 7758           line four"#
 7759    ));
 7760
 7761    cx.update_editor(|editor, window, cx| {
 7762        editor.add_selection_below(&Default::default(), window, cx);
 7763    });
 7764
 7765    // test multiple cursors expand in the same direction
 7766    cx.assert_editor_state(indoc!(
 7767        r#"line onˇe
 7768           liˇne twˇo
 7769           liˇne three
 7770           line four"#
 7771    ));
 7772
 7773    cx.update_editor(|editor, window, cx| {
 7774        editor.add_selection_below(&Default::default(), window, cx);
 7775    });
 7776
 7777    cx.update_editor(|editor, window, cx| {
 7778        editor.add_selection_below(&Default::default(), window, cx);
 7779    });
 7780
 7781    // test multiple cursors expand below overflow
 7782    cx.assert_editor_state(indoc!(
 7783        r#"line onˇe
 7784           liˇne twˇo
 7785           liˇne thˇree
 7786           liˇne foˇur"#
 7787    ));
 7788
 7789    cx.update_editor(|editor, window, cx| {
 7790        editor.add_selection_above(&Default::default(), window, cx);
 7791    });
 7792
 7793    // test multiple cursors retrieves back correctly
 7794    cx.assert_editor_state(indoc!(
 7795        r#"line onˇe
 7796           liˇne twˇo
 7797           liˇne thˇree
 7798           line four"#
 7799    ));
 7800
 7801    cx.update_editor(|editor, window, cx| {
 7802        editor.add_selection_above(&Default::default(), window, cx);
 7803    });
 7804
 7805    cx.update_editor(|editor, window, cx| {
 7806        editor.add_selection_above(&Default::default(), window, cx);
 7807    });
 7808
 7809    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 7810    cx.assert_editor_state(indoc!(
 7811        r#"liˇne onˇe
 7812           liˇne two
 7813           line three
 7814           line four"#
 7815    ));
 7816
 7817    cx.update_editor(|editor, window, cx| {
 7818        editor.undo_selection(&Default::default(), window, cx);
 7819    });
 7820
 7821    // test undo
 7822    cx.assert_editor_state(indoc!(
 7823        r#"line onˇe
 7824           liˇne twˇo
 7825           line three
 7826           line four"#
 7827    ));
 7828
 7829    cx.update_editor(|editor, window, cx| {
 7830        editor.redo_selection(&Default::default(), window, cx);
 7831    });
 7832
 7833    // test redo
 7834    cx.assert_editor_state(indoc!(
 7835        r#"liˇne onˇe
 7836           liˇne two
 7837           line three
 7838           line four"#
 7839    ));
 7840
 7841    cx.set_state(indoc!(
 7842        r#"abcd
 7843           ef«ghˇ»
 7844           ijkl
 7845           «mˇ»nop"#
 7846    ));
 7847
 7848    cx.update_editor(|editor, window, cx| {
 7849        editor.add_selection_above(&Default::default(), window, cx);
 7850    });
 7851
 7852    // test multiple selections expand in the same direction
 7853    cx.assert_editor_state(indoc!(
 7854        r#"ab«cdˇ»
 7855           ef«ghˇ»
 7856           «iˇ»jkl
 7857           «mˇ»nop"#
 7858    ));
 7859
 7860    cx.update_editor(|editor, window, cx| {
 7861        editor.add_selection_above(&Default::default(), window, cx);
 7862    });
 7863
 7864    // test multiple selection upward overflow
 7865    cx.assert_editor_state(indoc!(
 7866        r#"ab«cdˇ»
 7867           «eˇ»f«ghˇ»
 7868           «iˇ»jkl
 7869           «mˇ»nop"#
 7870    ));
 7871
 7872    cx.update_editor(|editor, window, cx| {
 7873        editor.add_selection_below(&Default::default(), window, cx);
 7874    });
 7875
 7876    // test multiple selection retrieves back correctly
 7877    cx.assert_editor_state(indoc!(
 7878        r#"abcd
 7879           ef«ghˇ»
 7880           «iˇ»jkl
 7881           «mˇ»nop"#
 7882    ));
 7883
 7884    cx.update_editor(|editor, window, cx| {
 7885        editor.add_selection_below(&Default::default(), window, cx);
 7886    });
 7887
 7888    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 7889    cx.assert_editor_state(indoc!(
 7890        r#"abcd
 7891           ef«ghˇ»
 7892           ij«klˇ»
 7893           «mˇ»nop"#
 7894    ));
 7895
 7896    cx.update_editor(|editor, window, cx| {
 7897        editor.undo_selection(&Default::default(), window, cx);
 7898    });
 7899
 7900    // test undo
 7901    cx.assert_editor_state(indoc!(
 7902        r#"abcd
 7903           ef«ghˇ»
 7904           «iˇ»jkl
 7905           «mˇ»nop"#
 7906    ));
 7907
 7908    cx.update_editor(|editor, window, cx| {
 7909        editor.redo_selection(&Default::default(), window, cx);
 7910    });
 7911
 7912    // test redo
 7913    cx.assert_editor_state(indoc!(
 7914        r#"abcd
 7915           ef«ghˇ»
 7916           ij«klˇ»
 7917           «mˇ»nop"#
 7918    ));
 7919}
 7920
 7921#[gpui::test]
 7922async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 7923    init_test(cx, |_| {});
 7924    let mut cx = EditorTestContext::new(cx).await;
 7925
 7926    cx.set_state(indoc!(
 7927        r#"line onˇe
 7928           liˇne two
 7929           line three
 7930           line four"#
 7931    ));
 7932
 7933    cx.update_editor(|editor, window, cx| {
 7934        editor.add_selection_below(&Default::default(), window, cx);
 7935        editor.add_selection_below(&Default::default(), window, cx);
 7936        editor.add_selection_below(&Default::default(), window, cx);
 7937    });
 7938
 7939    // initial state with two multi cursor groups
 7940    cx.assert_editor_state(indoc!(
 7941        r#"line onˇe
 7942           liˇne twˇo
 7943           liˇne thˇree
 7944           liˇne foˇur"#
 7945    ));
 7946
 7947    // add single cursor in middle - simulate opt click
 7948    cx.update_editor(|editor, window, cx| {
 7949        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 7950        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 7951        editor.end_selection(window, cx);
 7952    });
 7953
 7954    cx.assert_editor_state(indoc!(
 7955        r#"line onˇe
 7956           liˇne twˇo
 7957           liˇneˇ thˇree
 7958           liˇne foˇur"#
 7959    ));
 7960
 7961    cx.update_editor(|editor, window, cx| {
 7962        editor.add_selection_above(&Default::default(), window, cx);
 7963    });
 7964
 7965    // test new added selection expands above and existing selection shrinks
 7966    cx.assert_editor_state(indoc!(
 7967        r#"line onˇe
 7968           liˇneˇ twˇo
 7969           liˇneˇ thˇree
 7970           line four"#
 7971    ));
 7972
 7973    cx.update_editor(|editor, window, cx| {
 7974        editor.add_selection_above(&Default::default(), window, cx);
 7975    });
 7976
 7977    // test new added selection expands above and existing selection shrinks
 7978    cx.assert_editor_state(indoc!(
 7979        r#"lineˇ onˇe
 7980           liˇneˇ twˇo
 7981           lineˇ three
 7982           line four"#
 7983    ));
 7984
 7985    // intial state with two selection groups
 7986    cx.set_state(indoc!(
 7987        r#"abcd
 7988           ef«ghˇ»
 7989           ijkl
 7990           «mˇ»nop"#
 7991    ));
 7992
 7993    cx.update_editor(|editor, window, cx| {
 7994        editor.add_selection_above(&Default::default(), window, cx);
 7995        editor.add_selection_above(&Default::default(), window, cx);
 7996    });
 7997
 7998    cx.assert_editor_state(indoc!(
 7999        r#"ab«cdˇ»
 8000           «eˇ»f«ghˇ»
 8001           «iˇ»jkl
 8002           «mˇ»nop"#
 8003    ));
 8004
 8005    // add single selection in middle - simulate opt drag
 8006    cx.update_editor(|editor, window, cx| {
 8007        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 8008        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 8009        editor.update_selection(
 8010            DisplayPoint::new(DisplayRow(2), 4),
 8011            0,
 8012            gpui::Point::<f32>::default(),
 8013            window,
 8014            cx,
 8015        );
 8016        editor.end_selection(window, cx);
 8017    });
 8018
 8019    cx.assert_editor_state(indoc!(
 8020        r#"ab«cdˇ»
 8021           «eˇ»f«ghˇ»
 8022           «iˇ»jk«lˇ»
 8023           «mˇ»nop"#
 8024    ));
 8025
 8026    cx.update_editor(|editor, window, cx| {
 8027        editor.add_selection_below(&Default::default(), window, cx);
 8028    });
 8029
 8030    // test new added selection expands below, others shrinks from above
 8031    cx.assert_editor_state(indoc!(
 8032        r#"abcd
 8033           ef«ghˇ»
 8034           «iˇ»jk«lˇ»
 8035           «mˇ»no«pˇ»"#
 8036    ));
 8037}
 8038
 8039#[gpui::test]
 8040async fn test_select_next(cx: &mut TestAppContext) {
 8041    init_test(cx, |_| {});
 8042
 8043    let mut cx = EditorTestContext::new(cx).await;
 8044    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8045
 8046    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8047        .unwrap();
 8048    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8049
 8050    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8051        .unwrap();
 8052    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8053
 8054    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8055    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8056
 8057    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8058    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8059
 8060    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8061        .unwrap();
 8062    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8063
 8064    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8065        .unwrap();
 8066    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8067
 8068    // Test selection direction should be preserved
 8069    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8070
 8071    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8072        .unwrap();
 8073    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 8074}
 8075
 8076#[gpui::test]
 8077async fn test_select_all_matches(cx: &mut TestAppContext) {
 8078    init_test(cx, |_| {});
 8079
 8080    let mut cx = EditorTestContext::new(cx).await;
 8081
 8082    // Test caret-only selections
 8083    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8084    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8085        .unwrap();
 8086    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8087
 8088    // Test left-to-right selections
 8089    cx.set_state("abc\n«abcˇ»\nabc");
 8090    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8091        .unwrap();
 8092    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 8093
 8094    // Test right-to-left selections
 8095    cx.set_state("abc\n«ˇabc»\nabc");
 8096    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8097        .unwrap();
 8098    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 8099
 8100    // Test selecting whitespace with caret selection
 8101    cx.set_state("abc\nˇ   abc\nabc");
 8102    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8103        .unwrap();
 8104    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 8105
 8106    // Test selecting whitespace with left-to-right selection
 8107    cx.set_state("abc\n«ˇ  »abc\nabc");
 8108    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8109        .unwrap();
 8110    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 8111
 8112    // Test no matches with right-to-left selection
 8113    cx.set_state("abc\n«  ˇ»abc\nabc");
 8114    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8115        .unwrap();
 8116    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 8117
 8118    // Test with a single word and clip_at_line_ends=true (#29823)
 8119    cx.set_state("aˇbc");
 8120    cx.update_editor(|e, window, cx| {
 8121        e.set_clip_at_line_ends(true, cx);
 8122        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8123        e.set_clip_at_line_ends(false, cx);
 8124    });
 8125    cx.assert_editor_state("«abcˇ»");
 8126}
 8127
 8128#[gpui::test]
 8129async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 8130    init_test(cx, |_| {});
 8131
 8132    let mut cx = EditorTestContext::new(cx).await;
 8133
 8134    let large_body_1 = "\nd".repeat(200);
 8135    let large_body_2 = "\ne".repeat(200);
 8136
 8137    cx.set_state(&format!(
 8138        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 8139    ));
 8140    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 8141        let scroll_position = editor.scroll_position(cx);
 8142        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 8143        scroll_position
 8144    });
 8145
 8146    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8147        .unwrap();
 8148    cx.assert_editor_state(&format!(
 8149        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 8150    ));
 8151    let scroll_position_after_selection =
 8152        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 8153    assert_eq!(
 8154        initial_scroll_position, scroll_position_after_selection,
 8155        "Scroll position should not change after selecting all matches"
 8156    );
 8157}
 8158
 8159#[gpui::test]
 8160async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 8161    init_test(cx, |_| {});
 8162
 8163    let mut cx = EditorLspTestContext::new_rust(
 8164        lsp::ServerCapabilities {
 8165            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8166            ..Default::default()
 8167        },
 8168        cx,
 8169    )
 8170    .await;
 8171
 8172    cx.set_state(indoc! {"
 8173        line 1
 8174        line 2
 8175        linˇe 3
 8176        line 4
 8177        line 5
 8178    "});
 8179
 8180    // Make an edit
 8181    cx.update_editor(|editor, window, cx| {
 8182        editor.handle_input("X", window, cx);
 8183    });
 8184
 8185    // Move cursor to a different position
 8186    cx.update_editor(|editor, window, cx| {
 8187        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8188            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 8189        });
 8190    });
 8191
 8192    cx.assert_editor_state(indoc! {"
 8193        line 1
 8194        line 2
 8195        linXe 3
 8196        line 4
 8197        liˇne 5
 8198    "});
 8199
 8200    cx.lsp
 8201        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 8202            Ok(Some(vec![lsp::TextEdit::new(
 8203                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 8204                "PREFIX ".to_string(),
 8205            )]))
 8206        });
 8207
 8208    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 8209        .unwrap()
 8210        .await
 8211        .unwrap();
 8212
 8213    cx.assert_editor_state(indoc! {"
 8214        PREFIX line 1
 8215        line 2
 8216        linXe 3
 8217        line 4
 8218        liˇne 5
 8219    "});
 8220
 8221    // Undo formatting
 8222    cx.update_editor(|editor, window, cx| {
 8223        editor.undo(&Default::default(), window, cx);
 8224    });
 8225
 8226    // Verify cursor moved back to position after edit
 8227    cx.assert_editor_state(indoc! {"
 8228        line 1
 8229        line 2
 8230        linXˇe 3
 8231        line 4
 8232        line 5
 8233    "});
 8234}
 8235
 8236#[gpui::test]
 8237async fn test_undo_edit_prediction_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 8238    init_test(cx, |_| {});
 8239
 8240    let mut cx = EditorTestContext::new(cx).await;
 8241
 8242    let provider = cx.new(|_| FakeEditPredictionProvider::default());
 8243    cx.update_editor(|editor, window, cx| {
 8244        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 8245    });
 8246
 8247    cx.set_state(indoc! {"
 8248        line 1
 8249        line 2
 8250        linˇe 3
 8251        line 4
 8252        line 5
 8253        line 6
 8254        line 7
 8255        line 8
 8256        line 9
 8257        line 10
 8258    "});
 8259
 8260    let snapshot = cx.buffer_snapshot();
 8261    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 8262
 8263    cx.update(|_, cx| {
 8264        provider.update(cx, |provider, _| {
 8265            provider.set_edit_prediction(Some(edit_prediction::EditPrediction {
 8266                id: None,
 8267                edits: vec![(edit_position..edit_position, "X".into())],
 8268                edit_preview: None,
 8269            }))
 8270        })
 8271    });
 8272
 8273    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
 8274    cx.update_editor(|editor, window, cx| {
 8275        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 8276    });
 8277
 8278    cx.assert_editor_state(indoc! {"
 8279        line 1
 8280        line 2
 8281        lineXˇ 3
 8282        line 4
 8283        line 5
 8284        line 6
 8285        line 7
 8286        line 8
 8287        line 9
 8288        line 10
 8289    "});
 8290
 8291    cx.update_editor(|editor, window, cx| {
 8292        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8293            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 8294        });
 8295    });
 8296
 8297    cx.assert_editor_state(indoc! {"
 8298        line 1
 8299        line 2
 8300        lineX 3
 8301        line 4
 8302        line 5
 8303        line 6
 8304        line 7
 8305        line 8
 8306        line 9
 8307        liˇne 10
 8308    "});
 8309
 8310    cx.update_editor(|editor, window, cx| {
 8311        editor.undo(&Default::default(), window, cx);
 8312    });
 8313
 8314    cx.assert_editor_state(indoc! {"
 8315        line 1
 8316        line 2
 8317        lineˇ 3
 8318        line 4
 8319        line 5
 8320        line 6
 8321        line 7
 8322        line 8
 8323        line 9
 8324        line 10
 8325    "});
 8326}
 8327
 8328#[gpui::test]
 8329async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 8330    init_test(cx, |_| {});
 8331
 8332    let mut cx = EditorTestContext::new(cx).await;
 8333    cx.set_state(
 8334        r#"let foo = 2;
 8335lˇet foo = 2;
 8336let fooˇ = 2;
 8337let foo = 2;
 8338let foo = ˇ2;"#,
 8339    );
 8340
 8341    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8342        .unwrap();
 8343    cx.assert_editor_state(
 8344        r#"let foo = 2;
 8345«letˇ» foo = 2;
 8346let «fooˇ» = 2;
 8347let foo = 2;
 8348let foo = «2ˇ»;"#,
 8349    );
 8350
 8351    // noop for multiple selections with different contents
 8352    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8353        .unwrap();
 8354    cx.assert_editor_state(
 8355        r#"let foo = 2;
 8356«letˇ» foo = 2;
 8357let «fooˇ» = 2;
 8358let foo = 2;
 8359let foo = «2ˇ»;"#,
 8360    );
 8361
 8362    // Test last selection direction should be preserved
 8363    cx.set_state(
 8364        r#"let foo = 2;
 8365let foo = 2;
 8366let «fooˇ» = 2;
 8367let «ˇfoo» = 2;
 8368let foo = 2;"#,
 8369    );
 8370
 8371    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8372        .unwrap();
 8373    cx.assert_editor_state(
 8374        r#"let foo = 2;
 8375let foo = 2;
 8376let «fooˇ» = 2;
 8377let «ˇfoo» = 2;
 8378let «ˇfoo» = 2;"#,
 8379    );
 8380}
 8381
 8382#[gpui::test]
 8383async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 8384    init_test(cx, |_| {});
 8385
 8386    let mut cx =
 8387        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 8388
 8389    cx.assert_editor_state(indoc! {"
 8390        ˇbbb
 8391        ccc
 8392
 8393        bbb
 8394        ccc
 8395        "});
 8396    cx.dispatch_action(SelectPrevious::default());
 8397    cx.assert_editor_state(indoc! {"
 8398                «bbbˇ»
 8399                ccc
 8400
 8401                bbb
 8402                ccc
 8403                "});
 8404    cx.dispatch_action(SelectPrevious::default());
 8405    cx.assert_editor_state(indoc! {"
 8406                «bbbˇ»
 8407                ccc
 8408
 8409                «bbbˇ»
 8410                ccc
 8411                "});
 8412}
 8413
 8414#[gpui::test]
 8415async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 8416    init_test(cx, |_| {});
 8417
 8418    let mut cx = EditorTestContext::new(cx).await;
 8419    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8420
 8421    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8422        .unwrap();
 8423    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8424
 8425    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8426        .unwrap();
 8427    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8428
 8429    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8430    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8431
 8432    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8433    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8434
 8435    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8436        .unwrap();
 8437    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 8438
 8439    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8440        .unwrap();
 8441    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8442}
 8443
 8444#[gpui::test]
 8445async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 8446    init_test(cx, |_| {});
 8447
 8448    let mut cx = EditorTestContext::new(cx).await;
 8449    cx.set_state("");
 8450
 8451    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8452        .unwrap();
 8453    cx.assert_editor_state("«aˇ»");
 8454    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8455        .unwrap();
 8456    cx.assert_editor_state("«aˇ»");
 8457}
 8458
 8459#[gpui::test]
 8460async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 8461    init_test(cx, |_| {});
 8462
 8463    let mut cx = EditorTestContext::new(cx).await;
 8464    cx.set_state(
 8465        r#"let foo = 2;
 8466lˇet foo = 2;
 8467let fooˇ = 2;
 8468let foo = 2;
 8469let foo = ˇ2;"#,
 8470    );
 8471
 8472    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8473        .unwrap();
 8474    cx.assert_editor_state(
 8475        r#"let foo = 2;
 8476«letˇ» foo = 2;
 8477let «fooˇ» = 2;
 8478let foo = 2;
 8479let foo = «2ˇ»;"#,
 8480    );
 8481
 8482    // noop for multiple selections with different contents
 8483    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8484        .unwrap();
 8485    cx.assert_editor_state(
 8486        r#"let foo = 2;
 8487«letˇ» foo = 2;
 8488let «fooˇ» = 2;
 8489let foo = 2;
 8490let foo = «2ˇ»;"#,
 8491    );
 8492}
 8493
 8494#[gpui::test]
 8495async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 8496    init_test(cx, |_| {});
 8497
 8498    let mut cx = EditorTestContext::new(cx).await;
 8499    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8500
 8501    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8502        .unwrap();
 8503    // selection direction is preserved
 8504    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 8505
 8506    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8507        .unwrap();
 8508    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 8509
 8510    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8511    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 8512
 8513    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8514    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 8515
 8516    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8517        .unwrap();
 8518    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 8519
 8520    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8521        .unwrap();
 8522    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 8523}
 8524
 8525#[gpui::test]
 8526async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 8527    init_test(cx, |_| {});
 8528
 8529    let language = Arc::new(Language::new(
 8530        LanguageConfig::default(),
 8531        Some(tree_sitter_rust::LANGUAGE.into()),
 8532    ));
 8533
 8534    let text = r#"
 8535        use mod1::mod2::{mod3, mod4};
 8536
 8537        fn fn_1(param1: bool, param2: &str) {
 8538            let var1 = "text";
 8539        }
 8540    "#
 8541    .unindent();
 8542
 8543    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8544    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8545    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8546
 8547    editor
 8548        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8549        .await;
 8550
 8551    editor.update_in(cx, |editor, window, cx| {
 8552        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8553            s.select_display_ranges([
 8554                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 8555                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 8556                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 8557            ]);
 8558        });
 8559        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8560    });
 8561    editor.update(cx, |editor, cx| {
 8562        assert_text_with_selections(
 8563            editor,
 8564            indoc! {r#"
 8565                use mod1::mod2::{mod3, «mod4ˇ»};
 8566
 8567                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8568                    let var1 = "«ˇtext»";
 8569                }
 8570            "#},
 8571            cx,
 8572        );
 8573    });
 8574
 8575    editor.update_in(cx, |editor, window, cx| {
 8576        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8577    });
 8578    editor.update(cx, |editor, cx| {
 8579        assert_text_with_selections(
 8580            editor,
 8581            indoc! {r#"
 8582                use mod1::mod2::«{mod3, mod4}ˇ»;
 8583
 8584                «ˇfn fn_1(param1: bool, param2: &str) {
 8585                    let var1 = "text";
 8586 8587            "#},
 8588            cx,
 8589        );
 8590    });
 8591
 8592    editor.update_in(cx, |editor, window, cx| {
 8593        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8594    });
 8595    assert_eq!(
 8596        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 8597        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 8598    );
 8599
 8600    // Trying to expand the selected syntax node one more time has no effect.
 8601    editor.update_in(cx, |editor, window, cx| {
 8602        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8603    });
 8604    assert_eq!(
 8605        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 8606        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 8607    );
 8608
 8609    editor.update_in(cx, |editor, window, cx| {
 8610        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8611    });
 8612    editor.update(cx, |editor, cx| {
 8613        assert_text_with_selections(
 8614            editor,
 8615            indoc! {r#"
 8616                use mod1::mod2::«{mod3, mod4}ˇ»;
 8617
 8618                «ˇfn fn_1(param1: bool, param2: &str) {
 8619                    let var1 = "text";
 8620 8621            "#},
 8622            cx,
 8623        );
 8624    });
 8625
 8626    editor.update_in(cx, |editor, window, cx| {
 8627        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8628    });
 8629    editor.update(cx, |editor, cx| {
 8630        assert_text_with_selections(
 8631            editor,
 8632            indoc! {r#"
 8633                use mod1::mod2::{mod3, «mod4ˇ»};
 8634
 8635                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8636                    let var1 = "«ˇtext»";
 8637                }
 8638            "#},
 8639            cx,
 8640        );
 8641    });
 8642
 8643    editor.update_in(cx, |editor, window, cx| {
 8644        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8645    });
 8646    editor.update(cx, |editor, cx| {
 8647        assert_text_with_selections(
 8648            editor,
 8649            indoc! {r#"
 8650                use mod1::mod2::{mod3, moˇd4};
 8651
 8652                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 8653                    let var1 = "teˇxt";
 8654                }
 8655            "#},
 8656            cx,
 8657        );
 8658    });
 8659
 8660    // Trying to shrink the selected syntax node one more time has no effect.
 8661    editor.update_in(cx, |editor, window, cx| {
 8662        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8663    });
 8664    editor.update_in(cx, |editor, _, cx| {
 8665        assert_text_with_selections(
 8666            editor,
 8667            indoc! {r#"
 8668                use mod1::mod2::{mod3, moˇd4};
 8669
 8670                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 8671                    let var1 = "teˇxt";
 8672                }
 8673            "#},
 8674            cx,
 8675        );
 8676    });
 8677
 8678    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 8679    // a fold.
 8680    editor.update_in(cx, |editor, window, cx| {
 8681        editor.fold_creases(
 8682            vec![
 8683                Crease::simple(
 8684                    Point::new(0, 21)..Point::new(0, 24),
 8685                    FoldPlaceholder::test(),
 8686                ),
 8687                Crease::simple(
 8688                    Point::new(3, 20)..Point::new(3, 22),
 8689                    FoldPlaceholder::test(),
 8690                ),
 8691            ],
 8692            true,
 8693            window,
 8694            cx,
 8695        );
 8696        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8697    });
 8698    editor.update(cx, |editor, cx| {
 8699        assert_text_with_selections(
 8700            editor,
 8701            indoc! {r#"
 8702                use mod1::mod2::«{mod3, mod4}ˇ»;
 8703
 8704                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8705                    let var1 = "«ˇtext»";
 8706                }
 8707            "#},
 8708            cx,
 8709        );
 8710    });
 8711}
 8712
 8713#[gpui::test]
 8714async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 8715    init_test(cx, |_| {});
 8716
 8717    let language = Arc::new(Language::new(
 8718        LanguageConfig::default(),
 8719        Some(tree_sitter_rust::LANGUAGE.into()),
 8720    ));
 8721
 8722    let text = "let a = 2;";
 8723
 8724    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8725    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8726    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8727
 8728    editor
 8729        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8730        .await;
 8731
 8732    // Test case 1: Cursor at end of word
 8733    editor.update_in(cx, |editor, window, cx| {
 8734        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8735            s.select_display_ranges([
 8736                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 8737            ]);
 8738        });
 8739    });
 8740    editor.update(cx, |editor, cx| {
 8741        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 8742    });
 8743    editor.update_in(cx, |editor, window, cx| {
 8744        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8745    });
 8746    editor.update(cx, |editor, cx| {
 8747        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 8748    });
 8749    editor.update_in(cx, |editor, window, cx| {
 8750        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8751    });
 8752    editor.update(cx, |editor, cx| {
 8753        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 8754    });
 8755
 8756    // Test case 2: Cursor at end of statement
 8757    editor.update_in(cx, |editor, window, cx| {
 8758        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8759            s.select_display_ranges([
 8760                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 8761            ]);
 8762        });
 8763    });
 8764    editor.update(cx, |editor, cx| {
 8765        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 8766    });
 8767    editor.update_in(cx, |editor, window, cx| {
 8768        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8769    });
 8770    editor.update(cx, |editor, cx| {
 8771        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 8772    });
 8773}
 8774
 8775#[gpui::test]
 8776async fn test_select_larger_syntax_node_for_cursor_at_symbol(cx: &mut TestAppContext) {
 8777    init_test(cx, |_| {});
 8778
 8779    let language = Arc::new(Language::new(
 8780        LanguageConfig {
 8781            name: "JavaScript".into(),
 8782            ..Default::default()
 8783        },
 8784        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 8785    ));
 8786
 8787    let text = r#"
 8788        let a = {
 8789            key: "value",
 8790        };
 8791    "#
 8792    .unindent();
 8793
 8794    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8795    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8796    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8797
 8798    editor
 8799        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8800        .await;
 8801
 8802    // Test case 1: Cursor after '{'
 8803    editor.update_in(cx, |editor, window, cx| {
 8804        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8805            s.select_display_ranges([
 8806                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 9)
 8807            ]);
 8808        });
 8809    });
 8810    editor.update(cx, |editor, cx| {
 8811        assert_text_with_selections(
 8812            editor,
 8813            indoc! {r#"
 8814                let a = {ˇ
 8815                    key: "value",
 8816                };
 8817            "#},
 8818            cx,
 8819        );
 8820    });
 8821    editor.update_in(cx, |editor, window, cx| {
 8822        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8823    });
 8824    editor.update(cx, |editor, cx| {
 8825        assert_text_with_selections(
 8826            editor,
 8827            indoc! {r#"
 8828                let a = «ˇ{
 8829                    key: "value",
 8830                }»;
 8831            "#},
 8832            cx,
 8833        );
 8834    });
 8835
 8836    // Test case 2: Cursor after ':'
 8837    editor.update_in(cx, |editor, window, cx| {
 8838        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8839            s.select_display_ranges([
 8840                DisplayPoint::new(DisplayRow(1), 8)..DisplayPoint::new(DisplayRow(1), 8)
 8841            ]);
 8842        });
 8843    });
 8844    editor.update(cx, |editor, cx| {
 8845        assert_text_with_selections(
 8846            editor,
 8847            indoc! {r#"
 8848                let a = {
 8849                    key:ˇ "value",
 8850                };
 8851            "#},
 8852            cx,
 8853        );
 8854    });
 8855    editor.update_in(cx, |editor, window, cx| {
 8856        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8857    });
 8858    editor.update(cx, |editor, cx| {
 8859        assert_text_with_selections(
 8860            editor,
 8861            indoc! {r#"
 8862                let a = {
 8863                    «ˇkey: "value"»,
 8864                };
 8865            "#},
 8866            cx,
 8867        );
 8868    });
 8869    editor.update_in(cx, |editor, window, cx| {
 8870        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8871    });
 8872    editor.update(cx, |editor, cx| {
 8873        assert_text_with_selections(
 8874            editor,
 8875            indoc! {r#"
 8876                let a = «ˇ{
 8877                    key: "value",
 8878                }»;
 8879            "#},
 8880            cx,
 8881        );
 8882    });
 8883
 8884    // Test case 3: Cursor after ','
 8885    editor.update_in(cx, |editor, window, cx| {
 8886        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8887            s.select_display_ranges([
 8888                DisplayPoint::new(DisplayRow(1), 17)..DisplayPoint::new(DisplayRow(1), 17)
 8889            ]);
 8890        });
 8891    });
 8892    editor.update(cx, |editor, cx| {
 8893        assert_text_with_selections(
 8894            editor,
 8895            indoc! {r#"
 8896                let a = {
 8897                    key: "value",ˇ
 8898                };
 8899            "#},
 8900            cx,
 8901        );
 8902    });
 8903    editor.update_in(cx, |editor, window, cx| {
 8904        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8905    });
 8906    editor.update(cx, |editor, cx| {
 8907        assert_text_with_selections(
 8908            editor,
 8909            indoc! {r#"
 8910                let a = «ˇ{
 8911                    key: "value",
 8912                }»;
 8913            "#},
 8914            cx,
 8915        );
 8916    });
 8917
 8918    // Test case 4: Cursor after ';'
 8919    editor.update_in(cx, |editor, window, cx| {
 8920        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8921            s.select_display_ranges([
 8922                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)
 8923            ]);
 8924        });
 8925    });
 8926    editor.update(cx, |editor, cx| {
 8927        assert_text_with_selections(
 8928            editor,
 8929            indoc! {r#"
 8930                let a = {
 8931                    key: "value",
 8932                };ˇ
 8933            "#},
 8934            cx,
 8935        );
 8936    });
 8937    editor.update_in(cx, |editor, window, cx| {
 8938        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8939    });
 8940    editor.update(cx, |editor, cx| {
 8941        assert_text_with_selections(
 8942            editor,
 8943            indoc! {r#"
 8944                «ˇlet a = {
 8945                    key: "value",
 8946                };
 8947                »"#},
 8948            cx,
 8949        );
 8950    });
 8951}
 8952
 8953#[gpui::test]
 8954async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 8955    init_test(cx, |_| {});
 8956
 8957    let language = Arc::new(Language::new(
 8958        LanguageConfig::default(),
 8959        Some(tree_sitter_rust::LANGUAGE.into()),
 8960    ));
 8961
 8962    let text = r#"
 8963        use mod1::mod2::{mod3, mod4};
 8964
 8965        fn fn_1(param1: bool, param2: &str) {
 8966            let var1 = "hello world";
 8967        }
 8968    "#
 8969    .unindent();
 8970
 8971    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8972    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8973    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8974
 8975    editor
 8976        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8977        .await;
 8978
 8979    // Test 1: Cursor on a letter of a string word
 8980    editor.update_in(cx, |editor, window, cx| {
 8981        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8982            s.select_display_ranges([
 8983                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 8984            ]);
 8985        });
 8986    });
 8987    editor.update_in(cx, |editor, window, cx| {
 8988        assert_text_with_selections(
 8989            editor,
 8990            indoc! {r#"
 8991                use mod1::mod2::{mod3, mod4};
 8992
 8993                fn fn_1(param1: bool, param2: &str) {
 8994                    let var1 = "hˇello world";
 8995                }
 8996            "#},
 8997            cx,
 8998        );
 8999        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9000        assert_text_with_selections(
 9001            editor,
 9002            indoc! {r#"
 9003                use mod1::mod2::{mod3, mod4};
 9004
 9005                fn fn_1(param1: bool, param2: &str) {
 9006                    let var1 = "«ˇhello» world";
 9007                }
 9008            "#},
 9009            cx,
 9010        );
 9011    });
 9012
 9013    // Test 2: Partial selection within a word
 9014    editor.update_in(cx, |editor, window, cx| {
 9015        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9016            s.select_display_ranges([
 9017                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 9018            ]);
 9019        });
 9020    });
 9021    editor.update_in(cx, |editor, window, cx| {
 9022        assert_text_with_selections(
 9023            editor,
 9024            indoc! {r#"
 9025                use mod1::mod2::{mod3, mod4};
 9026
 9027                fn fn_1(param1: bool, param2: &str) {
 9028                    let var1 = "h«elˇ»lo world";
 9029                }
 9030            "#},
 9031            cx,
 9032        );
 9033        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, 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 = "«ˇhello» world";
 9041                }
 9042            "#},
 9043            cx,
 9044        );
 9045    });
 9046
 9047    // Test 3: Complete word already selected
 9048    editor.update_in(cx, |editor, window, cx| {
 9049        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9050            s.select_display_ranges([
 9051                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 9052            ]);
 9053        });
 9054    });
 9055    editor.update_in(cx, |editor, window, cx| {
 9056        assert_text_with_selections(
 9057            editor,
 9058            indoc! {r#"
 9059                use mod1::mod2::{mod3, mod4};
 9060
 9061                fn fn_1(param1: bool, param2: &str) {
 9062                    let var1 = "«helloˇ» world";
 9063                }
 9064            "#},
 9065            cx,
 9066        );
 9067        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9068        assert_text_with_selections(
 9069            editor,
 9070            indoc! {r#"
 9071                use mod1::mod2::{mod3, mod4};
 9072
 9073                fn fn_1(param1: bool, param2: &str) {
 9074                    let var1 = "«hello worldˇ»";
 9075                }
 9076            "#},
 9077            cx,
 9078        );
 9079    });
 9080
 9081    // Test 4: Selection spanning across words
 9082    editor.update_in(cx, |editor, window, cx| {
 9083        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9084            s.select_display_ranges([
 9085                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 9086            ]);
 9087        });
 9088    });
 9089    editor.update_in(cx, |editor, window, cx| {
 9090        assert_text_with_selections(
 9091            editor,
 9092            indoc! {r#"
 9093                use mod1::mod2::{mod3, mod4};
 9094
 9095                fn fn_1(param1: bool, param2: &str) {
 9096                    let var1 = "hel«lo woˇ»rld";
 9097                }
 9098            "#},
 9099            cx,
 9100        );
 9101        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9102        assert_text_with_selections(
 9103            editor,
 9104            indoc! {r#"
 9105                use mod1::mod2::{mod3, mod4};
 9106
 9107                fn fn_1(param1: bool, param2: &str) {
 9108                    let var1 = "«ˇhello world»";
 9109                }
 9110            "#},
 9111            cx,
 9112        );
 9113    });
 9114
 9115    // Test 5: Expansion beyond string
 9116    editor.update_in(cx, |editor, window, cx| {
 9117        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9118        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9119        assert_text_with_selections(
 9120            editor,
 9121            indoc! {r#"
 9122                use mod1::mod2::{mod3, mod4};
 9123
 9124                fn fn_1(param1: bool, param2: &str) {
 9125                    «ˇlet var1 = "hello world";»
 9126                }
 9127            "#},
 9128            cx,
 9129        );
 9130    });
 9131}
 9132
 9133#[gpui::test]
 9134async fn test_unwrap_syntax_nodes(cx: &mut gpui::TestAppContext) {
 9135    init_test(cx, |_| {});
 9136
 9137    let mut cx = EditorTestContext::new(cx).await;
 9138
 9139    let language = Arc::new(Language::new(
 9140        LanguageConfig::default(),
 9141        Some(tree_sitter_rust::LANGUAGE.into()),
 9142    ));
 9143
 9144    cx.update_buffer(|buffer, cx| {
 9145        buffer.set_language(Some(language), cx);
 9146    });
 9147
 9148    cx.set_state(indoc! { r#"use mod1::{mod2::{«mod3ˇ», mod4}, mod5::{mod6, «mod7ˇ»}};"# });
 9149    cx.update_editor(|editor, window, cx| {
 9150        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9151    });
 9152
 9153    cx.assert_editor_state(indoc! { r#"use mod1::{mod2::«mod3ˇ», mod5::«mod7ˇ»};"# });
 9154}
 9155
 9156#[gpui::test]
 9157async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 9158    init_test(cx, |_| {});
 9159
 9160    let base_text = r#"
 9161        impl A {
 9162            // this is an uncommitted comment
 9163
 9164            fn b() {
 9165                c();
 9166            }
 9167
 9168            // this is another uncommitted comment
 9169
 9170            fn d() {
 9171                // e
 9172                // f
 9173            }
 9174        }
 9175
 9176        fn g() {
 9177            // h
 9178        }
 9179    "#
 9180    .unindent();
 9181
 9182    let text = r#"
 9183        ˇimpl A {
 9184
 9185            fn b() {
 9186                c();
 9187            }
 9188
 9189            fn d() {
 9190                // e
 9191                // f
 9192            }
 9193        }
 9194
 9195        fn g() {
 9196            // h
 9197        }
 9198    "#
 9199    .unindent();
 9200
 9201    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9202    cx.set_state(&text);
 9203    cx.set_head_text(&base_text);
 9204    cx.update_editor(|editor, window, cx| {
 9205        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 9206    });
 9207
 9208    cx.assert_state_with_diff(
 9209        "
 9210        ˇimpl A {
 9211      -     // this is an uncommitted comment
 9212
 9213            fn b() {
 9214                c();
 9215            }
 9216
 9217      -     // this is another uncommitted comment
 9218      -
 9219            fn d() {
 9220                // e
 9221                // f
 9222            }
 9223        }
 9224
 9225        fn g() {
 9226            // h
 9227        }
 9228    "
 9229        .unindent(),
 9230    );
 9231
 9232    let expected_display_text = "
 9233        impl A {
 9234            // this is an uncommitted comment
 9235
 9236            fn b() {
 9237 9238            }
 9239
 9240            // this is another uncommitted comment
 9241
 9242            fn d() {
 9243 9244            }
 9245        }
 9246
 9247        fn g() {
 9248 9249        }
 9250        "
 9251    .unindent();
 9252
 9253    cx.update_editor(|editor, window, cx| {
 9254        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 9255        assert_eq!(editor.display_text(cx), expected_display_text);
 9256    });
 9257}
 9258
 9259#[gpui::test]
 9260async fn test_autoindent(cx: &mut TestAppContext) {
 9261    init_test(cx, |_| {});
 9262
 9263    let language = Arc::new(
 9264        Language::new(
 9265            LanguageConfig {
 9266                brackets: BracketPairConfig {
 9267                    pairs: vec![
 9268                        BracketPair {
 9269                            start: "{".to_string(),
 9270                            end: "}".to_string(),
 9271                            close: false,
 9272                            surround: false,
 9273                            newline: true,
 9274                        },
 9275                        BracketPair {
 9276                            start: "(".to_string(),
 9277                            end: ")".to_string(),
 9278                            close: false,
 9279                            surround: false,
 9280                            newline: true,
 9281                        },
 9282                    ],
 9283                    ..Default::default()
 9284                },
 9285                ..Default::default()
 9286            },
 9287            Some(tree_sitter_rust::LANGUAGE.into()),
 9288        )
 9289        .with_indents_query(
 9290            r#"
 9291                (_ "(" ")" @end) @indent
 9292                (_ "{" "}" @end) @indent
 9293            "#,
 9294        )
 9295        .unwrap(),
 9296    );
 9297
 9298    let text = "fn a() {}";
 9299
 9300    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9301    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9302    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9303    editor
 9304        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9305        .await;
 9306
 9307    editor.update_in(cx, |editor, window, cx| {
 9308        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9309            s.select_ranges([5..5, 8..8, 9..9])
 9310        });
 9311        editor.newline(&Newline, window, cx);
 9312        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 9313        assert_eq!(
 9314            editor.selections.ranges(cx),
 9315            &[
 9316                Point::new(1, 4)..Point::new(1, 4),
 9317                Point::new(3, 4)..Point::new(3, 4),
 9318                Point::new(5, 0)..Point::new(5, 0)
 9319            ]
 9320        );
 9321    });
 9322}
 9323
 9324#[gpui::test]
 9325async fn test_autoindent_disabled(cx: &mut TestAppContext) {
 9326    init_test(cx, |settings| settings.defaults.auto_indent = Some(false));
 9327
 9328    let language = Arc::new(
 9329        Language::new(
 9330            LanguageConfig {
 9331                brackets: BracketPairConfig {
 9332                    pairs: vec![
 9333                        BracketPair {
 9334                            start: "{".to_string(),
 9335                            end: "}".to_string(),
 9336                            close: false,
 9337                            surround: false,
 9338                            newline: true,
 9339                        },
 9340                        BracketPair {
 9341                            start: "(".to_string(),
 9342                            end: ")".to_string(),
 9343                            close: false,
 9344                            surround: false,
 9345                            newline: true,
 9346                        },
 9347                    ],
 9348                    ..Default::default()
 9349                },
 9350                ..Default::default()
 9351            },
 9352            Some(tree_sitter_rust::LANGUAGE.into()),
 9353        )
 9354        .with_indents_query(
 9355            r#"
 9356                (_ "(" ")" @end) @indent
 9357                (_ "{" "}" @end) @indent
 9358            "#,
 9359        )
 9360        .unwrap(),
 9361    );
 9362
 9363    let text = "fn a() {}";
 9364
 9365    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9366    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9367    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9368    editor
 9369        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9370        .await;
 9371
 9372    editor.update_in(cx, |editor, window, cx| {
 9373        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9374            s.select_ranges([5..5, 8..8, 9..9])
 9375        });
 9376        editor.newline(&Newline, window, cx);
 9377        assert_eq!(
 9378            editor.text(cx),
 9379            indoc!(
 9380                "
 9381                fn a(
 9382
 9383                ) {
 9384
 9385                }
 9386                "
 9387            )
 9388        );
 9389        assert_eq!(
 9390            editor.selections.ranges(cx),
 9391            &[
 9392                Point::new(1, 0)..Point::new(1, 0),
 9393                Point::new(3, 0)..Point::new(3, 0),
 9394                Point::new(5, 0)..Point::new(5, 0)
 9395            ]
 9396        );
 9397    });
 9398}
 9399
 9400#[gpui::test]
 9401async fn test_autoindent_disabled_with_nested_language(cx: &mut TestAppContext) {
 9402    init_test(cx, |settings| {
 9403        settings.defaults.auto_indent = Some(true);
 9404        settings.languages.0.insert(
 9405            "python".into(),
 9406            LanguageSettingsContent {
 9407                auto_indent: Some(false),
 9408                ..Default::default()
 9409            },
 9410        );
 9411    });
 9412
 9413    let mut cx = EditorTestContext::new(cx).await;
 9414
 9415    let injected_language = Arc::new(
 9416        Language::new(
 9417            LanguageConfig {
 9418                brackets: BracketPairConfig {
 9419                    pairs: vec![
 9420                        BracketPair {
 9421                            start: "{".to_string(),
 9422                            end: "}".to_string(),
 9423                            close: false,
 9424                            surround: false,
 9425                            newline: true,
 9426                        },
 9427                        BracketPair {
 9428                            start: "(".to_string(),
 9429                            end: ")".to_string(),
 9430                            close: true,
 9431                            surround: false,
 9432                            newline: true,
 9433                        },
 9434                    ],
 9435                    ..Default::default()
 9436                },
 9437                name: "python".into(),
 9438                ..Default::default()
 9439            },
 9440            Some(tree_sitter_python::LANGUAGE.into()),
 9441        )
 9442        .with_indents_query(
 9443            r#"
 9444                (_ "(" ")" @end) @indent
 9445                (_ "{" "}" @end) @indent
 9446            "#,
 9447        )
 9448        .unwrap(),
 9449    );
 9450
 9451    let language = Arc::new(
 9452        Language::new(
 9453            LanguageConfig {
 9454                brackets: BracketPairConfig {
 9455                    pairs: vec![
 9456                        BracketPair {
 9457                            start: "{".to_string(),
 9458                            end: "}".to_string(),
 9459                            close: false,
 9460                            surround: false,
 9461                            newline: true,
 9462                        },
 9463                        BracketPair {
 9464                            start: "(".to_string(),
 9465                            end: ")".to_string(),
 9466                            close: true,
 9467                            surround: false,
 9468                            newline: true,
 9469                        },
 9470                    ],
 9471                    ..Default::default()
 9472                },
 9473                name: LanguageName::new("rust"),
 9474                ..Default::default()
 9475            },
 9476            Some(tree_sitter_rust::LANGUAGE.into()),
 9477        )
 9478        .with_indents_query(
 9479            r#"
 9480                (_ "(" ")" @end) @indent
 9481                (_ "{" "}" @end) @indent
 9482            "#,
 9483        )
 9484        .unwrap()
 9485        .with_injection_query(
 9486            r#"
 9487            (macro_invocation
 9488                macro: (identifier) @_macro_name
 9489                (token_tree) @injection.content
 9490                (#set! injection.language "python"))
 9491           "#,
 9492        )
 9493        .unwrap(),
 9494    );
 9495
 9496    cx.language_registry().add(injected_language);
 9497    cx.language_registry().add(language.clone());
 9498
 9499    cx.update_buffer(|buffer, cx| {
 9500        buffer.set_language(Some(language), cx);
 9501    });
 9502
 9503    cx.set_state(r#"struct A {ˇ}"#);
 9504
 9505    cx.update_editor(|editor, window, cx| {
 9506        editor.newline(&Default::default(), window, cx);
 9507    });
 9508
 9509    cx.assert_editor_state(indoc!(
 9510        "struct A {
 9511            ˇ
 9512        }"
 9513    ));
 9514
 9515    cx.set_state(r#"select_biased!(ˇ)"#);
 9516
 9517    cx.update_editor(|editor, window, cx| {
 9518        editor.newline(&Default::default(), window, cx);
 9519        editor.handle_input("def ", window, cx);
 9520        editor.handle_input("(", window, cx);
 9521        editor.newline(&Default::default(), window, cx);
 9522        editor.handle_input("a", window, cx);
 9523    });
 9524
 9525    cx.assert_editor_state(indoc!(
 9526        "select_biased!(
 9527        def (
 9528 9529        )
 9530        )"
 9531    ));
 9532}
 9533
 9534#[gpui::test]
 9535async fn test_autoindent_selections(cx: &mut TestAppContext) {
 9536    init_test(cx, |_| {});
 9537
 9538    {
 9539        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9540        cx.set_state(indoc! {"
 9541            impl A {
 9542
 9543                fn b() {}
 9544
 9545            «fn c() {
 9546
 9547            }ˇ»
 9548            }
 9549        "});
 9550
 9551        cx.update_editor(|editor, window, cx| {
 9552            editor.autoindent(&Default::default(), window, cx);
 9553        });
 9554
 9555        cx.assert_editor_state(indoc! {"
 9556            impl A {
 9557
 9558                fn b() {}
 9559
 9560                «fn c() {
 9561
 9562                }ˇ»
 9563            }
 9564        "});
 9565    }
 9566
 9567    {
 9568        let mut cx = EditorTestContext::new_multibuffer(
 9569            cx,
 9570            [indoc! { "
 9571                impl A {
 9572                «
 9573                // a
 9574                fn b(){}
 9575                »
 9576                «
 9577                    }
 9578                    fn c(){}
 9579                »
 9580            "}],
 9581        );
 9582
 9583        let buffer = cx.update_editor(|editor, _, cx| {
 9584            let buffer = editor.buffer().update(cx, |buffer, _| {
 9585                buffer.all_buffers().iter().next().unwrap().clone()
 9586            });
 9587            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 9588            buffer
 9589        });
 9590
 9591        cx.run_until_parked();
 9592        cx.update_editor(|editor, window, cx| {
 9593            editor.select_all(&Default::default(), window, cx);
 9594            editor.autoindent(&Default::default(), window, cx)
 9595        });
 9596        cx.run_until_parked();
 9597
 9598        cx.update(|_, cx| {
 9599            assert_eq!(
 9600                buffer.read(cx).text(),
 9601                indoc! { "
 9602                    impl A {
 9603
 9604                        // a
 9605                        fn b(){}
 9606
 9607
 9608                    }
 9609                    fn c(){}
 9610
 9611                " }
 9612            )
 9613        });
 9614    }
 9615}
 9616
 9617#[gpui::test]
 9618async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 9619    init_test(cx, |_| {});
 9620
 9621    let mut cx = EditorTestContext::new(cx).await;
 9622
 9623    let language = Arc::new(Language::new(
 9624        LanguageConfig {
 9625            brackets: BracketPairConfig {
 9626                pairs: vec![
 9627                    BracketPair {
 9628                        start: "{".to_string(),
 9629                        end: "}".to_string(),
 9630                        close: true,
 9631                        surround: true,
 9632                        newline: true,
 9633                    },
 9634                    BracketPair {
 9635                        start: "(".to_string(),
 9636                        end: ")".to_string(),
 9637                        close: true,
 9638                        surround: true,
 9639                        newline: true,
 9640                    },
 9641                    BracketPair {
 9642                        start: "/*".to_string(),
 9643                        end: " */".to_string(),
 9644                        close: true,
 9645                        surround: true,
 9646                        newline: true,
 9647                    },
 9648                    BracketPair {
 9649                        start: "[".to_string(),
 9650                        end: "]".to_string(),
 9651                        close: false,
 9652                        surround: false,
 9653                        newline: true,
 9654                    },
 9655                    BracketPair {
 9656                        start: "\"".to_string(),
 9657                        end: "\"".to_string(),
 9658                        close: true,
 9659                        surround: true,
 9660                        newline: false,
 9661                    },
 9662                    BracketPair {
 9663                        start: "<".to_string(),
 9664                        end: ">".to_string(),
 9665                        close: false,
 9666                        surround: true,
 9667                        newline: true,
 9668                    },
 9669                ],
 9670                ..Default::default()
 9671            },
 9672            autoclose_before: "})]".to_string(),
 9673            ..Default::default()
 9674        },
 9675        Some(tree_sitter_rust::LANGUAGE.into()),
 9676    ));
 9677
 9678    cx.language_registry().add(language.clone());
 9679    cx.update_buffer(|buffer, cx| {
 9680        buffer.set_language(Some(language), cx);
 9681    });
 9682
 9683    cx.set_state(
 9684        &r#"
 9685            🏀ˇ
 9686            εˇ
 9687            ❤️ˇ
 9688        "#
 9689        .unindent(),
 9690    );
 9691
 9692    // autoclose multiple nested brackets at multiple cursors
 9693    cx.update_editor(|editor, window, cx| {
 9694        editor.handle_input("{", window, cx);
 9695        editor.handle_input("{", window, cx);
 9696        editor.handle_input("{", window, cx);
 9697    });
 9698    cx.assert_editor_state(
 9699        &"
 9700            🏀{{{ˇ}}}
 9701            ε{{{ˇ}}}
 9702            ❤️{{{ˇ}}}
 9703        "
 9704        .unindent(),
 9705    );
 9706
 9707    // insert a different closing bracket
 9708    cx.update_editor(|editor, window, cx| {
 9709        editor.handle_input(")", window, cx);
 9710    });
 9711    cx.assert_editor_state(
 9712        &"
 9713            🏀{{{)ˇ}}}
 9714            ε{{{)ˇ}}}
 9715            ❤️{{{)ˇ}}}
 9716        "
 9717        .unindent(),
 9718    );
 9719
 9720    // skip over the auto-closed brackets when typing a closing bracket
 9721    cx.update_editor(|editor, window, cx| {
 9722        editor.move_right(&MoveRight, window, cx);
 9723        editor.handle_input("}", window, cx);
 9724        editor.handle_input("}", window, cx);
 9725        editor.handle_input("}", window, cx);
 9726    });
 9727    cx.assert_editor_state(
 9728        &"
 9729            🏀{{{)}}}}ˇ
 9730            ε{{{)}}}}ˇ
 9731            ❤️{{{)}}}}ˇ
 9732        "
 9733        .unindent(),
 9734    );
 9735
 9736    // autoclose multi-character pairs
 9737    cx.set_state(
 9738        &"
 9739            ˇ
 9740            ˇ
 9741        "
 9742        .unindent(),
 9743    );
 9744    cx.update_editor(|editor, window, cx| {
 9745        editor.handle_input("/", window, cx);
 9746        editor.handle_input("*", window, cx);
 9747    });
 9748    cx.assert_editor_state(
 9749        &"
 9750            /*ˇ */
 9751            /*ˇ */
 9752        "
 9753        .unindent(),
 9754    );
 9755
 9756    // one cursor autocloses a multi-character pair, one cursor
 9757    // does not autoclose.
 9758    cx.set_state(
 9759        &"
 9760 9761            ˇ
 9762        "
 9763        .unindent(),
 9764    );
 9765    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
 9766    cx.assert_editor_state(
 9767        &"
 9768            /*ˇ */
 9769 9770        "
 9771        .unindent(),
 9772    );
 9773
 9774    // Don't autoclose if the next character isn't whitespace and isn't
 9775    // listed in the language's "autoclose_before" section.
 9776    cx.set_state("ˇa b");
 9777    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 9778    cx.assert_editor_state("{ˇa b");
 9779
 9780    // Don't autoclose if `close` is false for the bracket pair
 9781    cx.set_state("ˇ");
 9782    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
 9783    cx.assert_editor_state("");
 9784
 9785    // Surround with brackets if text is selected
 9786    cx.set_state("«aˇ» b");
 9787    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 9788    cx.assert_editor_state("{«aˇ»} b");
 9789
 9790    // Autoclose when not immediately after a word character
 9791    cx.set_state("a ˇ");
 9792    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9793    cx.assert_editor_state("a \"ˇ\"");
 9794
 9795    // Autoclose pair where the start and end characters are the same
 9796    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9797    cx.assert_editor_state("a \"\"ˇ");
 9798
 9799    // Don't autoclose when immediately after a word character
 9800    cx.set_state("");
 9801    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9802    cx.assert_editor_state("a\"ˇ");
 9803
 9804    // Do autoclose when after a non-word character
 9805    cx.set_state("");
 9806    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9807    cx.assert_editor_state("{\"ˇ\"");
 9808
 9809    // Non identical pairs autoclose regardless of preceding character
 9810    cx.set_state("");
 9811    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 9812    cx.assert_editor_state("a{ˇ}");
 9813
 9814    // Don't autoclose pair if autoclose is disabled
 9815    cx.set_state("ˇ");
 9816    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 9817    cx.assert_editor_state("");
 9818
 9819    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
 9820    cx.set_state("«aˇ» b");
 9821    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 9822    cx.assert_editor_state("<«aˇ»> b");
 9823}
 9824
 9825#[gpui::test]
 9826async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
 9827    init_test(cx, |settings| {
 9828        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 9829    });
 9830
 9831    let mut cx = EditorTestContext::new(cx).await;
 9832
 9833    let language = Arc::new(Language::new(
 9834        LanguageConfig {
 9835            brackets: BracketPairConfig {
 9836                pairs: vec![
 9837                    BracketPair {
 9838                        start: "{".to_string(),
 9839                        end: "}".to_string(),
 9840                        close: true,
 9841                        surround: true,
 9842                        newline: true,
 9843                    },
 9844                    BracketPair {
 9845                        start: "(".to_string(),
 9846                        end: ")".to_string(),
 9847                        close: true,
 9848                        surround: true,
 9849                        newline: true,
 9850                    },
 9851                    BracketPair {
 9852                        start: "[".to_string(),
 9853                        end: "]".to_string(),
 9854                        close: false,
 9855                        surround: false,
 9856                        newline: true,
 9857                    },
 9858                ],
 9859                ..Default::default()
 9860            },
 9861            autoclose_before: "})]".to_string(),
 9862            ..Default::default()
 9863        },
 9864        Some(tree_sitter_rust::LANGUAGE.into()),
 9865    ));
 9866
 9867    cx.language_registry().add(language.clone());
 9868    cx.update_buffer(|buffer, cx| {
 9869        buffer.set_language(Some(language), cx);
 9870    });
 9871
 9872    cx.set_state(
 9873        &"
 9874            ˇ
 9875            ˇ
 9876            ˇ
 9877        "
 9878        .unindent(),
 9879    );
 9880
 9881    // ensure only matching closing brackets are skipped over
 9882    cx.update_editor(|editor, window, cx| {
 9883        editor.handle_input("}", window, cx);
 9884        editor.move_left(&MoveLeft, window, cx);
 9885        editor.handle_input(")", window, cx);
 9886        editor.move_left(&MoveLeft, window, cx);
 9887    });
 9888    cx.assert_editor_state(
 9889        &"
 9890            ˇ)}
 9891            ˇ)}
 9892            ˇ)}
 9893        "
 9894        .unindent(),
 9895    );
 9896
 9897    // skip-over closing brackets at multiple cursors
 9898    cx.update_editor(|editor, window, cx| {
 9899        editor.handle_input(")", window, cx);
 9900        editor.handle_input("}", window, cx);
 9901    });
 9902    cx.assert_editor_state(
 9903        &"
 9904            )}ˇ
 9905            )}ˇ
 9906            )}ˇ
 9907        "
 9908        .unindent(),
 9909    );
 9910
 9911    // ignore non-close brackets
 9912    cx.update_editor(|editor, window, cx| {
 9913        editor.handle_input("]", window, cx);
 9914        editor.move_left(&MoveLeft, window, cx);
 9915        editor.handle_input("]", window, cx);
 9916    });
 9917    cx.assert_editor_state(
 9918        &"
 9919            )}]ˇ]
 9920            )}]ˇ]
 9921            )}]ˇ]
 9922        "
 9923        .unindent(),
 9924    );
 9925}
 9926
 9927#[gpui::test]
 9928async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
 9929    init_test(cx, |_| {});
 9930
 9931    let mut cx = EditorTestContext::new(cx).await;
 9932
 9933    let html_language = Arc::new(
 9934        Language::new(
 9935            LanguageConfig {
 9936                name: "HTML".into(),
 9937                brackets: BracketPairConfig {
 9938                    pairs: vec![
 9939                        BracketPair {
 9940                            start: "<".into(),
 9941                            end: ">".into(),
 9942                            close: true,
 9943                            ..Default::default()
 9944                        },
 9945                        BracketPair {
 9946                            start: "{".into(),
 9947                            end: "}".into(),
 9948                            close: true,
 9949                            ..Default::default()
 9950                        },
 9951                        BracketPair {
 9952                            start: "(".into(),
 9953                            end: ")".into(),
 9954                            close: true,
 9955                            ..Default::default()
 9956                        },
 9957                    ],
 9958                    ..Default::default()
 9959                },
 9960                autoclose_before: "})]>".into(),
 9961                ..Default::default()
 9962            },
 9963            Some(tree_sitter_html::LANGUAGE.into()),
 9964        )
 9965        .with_injection_query(
 9966            r#"
 9967            (script_element
 9968                (raw_text) @injection.content
 9969                (#set! injection.language "javascript"))
 9970            "#,
 9971        )
 9972        .unwrap(),
 9973    );
 9974
 9975    let javascript_language = Arc::new(Language::new(
 9976        LanguageConfig {
 9977            name: "JavaScript".into(),
 9978            brackets: BracketPairConfig {
 9979                pairs: vec![
 9980                    BracketPair {
 9981                        start: "/*".into(),
 9982                        end: " */".into(),
 9983                        close: true,
 9984                        ..Default::default()
 9985                    },
 9986                    BracketPair {
 9987                        start: "{".into(),
 9988                        end: "}".into(),
 9989                        close: true,
 9990                        ..Default::default()
 9991                    },
 9992                    BracketPair {
 9993                        start: "(".into(),
 9994                        end: ")".into(),
 9995                        close: true,
 9996                        ..Default::default()
 9997                    },
 9998                ],
 9999                ..Default::default()
10000            },
10001            autoclose_before: "})]>".into(),
10002            ..Default::default()
10003        },
10004        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10005    ));
10006
10007    cx.language_registry().add(html_language.clone());
10008    cx.language_registry().add(javascript_language);
10009    cx.executor().run_until_parked();
10010
10011    cx.update_buffer(|buffer, cx| {
10012        buffer.set_language(Some(html_language), cx);
10013    });
10014
10015    cx.set_state(
10016        &r#"
10017            <body>ˇ
10018                <script>
10019                    var x = 1;ˇ
10020                </script>
10021            </body>ˇ
10022        "#
10023        .unindent(),
10024    );
10025
10026    // Precondition: different languages are active at different locations.
10027    cx.update_editor(|editor, window, cx| {
10028        let snapshot = editor.snapshot(window, cx);
10029        let cursors = editor.selections.ranges::<usize>(cx);
10030        let languages = cursors
10031            .iter()
10032            .map(|c| snapshot.language_at(c.start).unwrap().name())
10033            .collect::<Vec<_>>();
10034        assert_eq!(
10035            languages,
10036            &["HTML".into(), "JavaScript".into(), "HTML".into()]
10037        );
10038    });
10039
10040    // Angle brackets autoclose in HTML, but not JavaScript.
10041    cx.update_editor(|editor, window, cx| {
10042        editor.handle_input("<", window, cx);
10043        editor.handle_input("a", window, cx);
10044    });
10045    cx.assert_editor_state(
10046        &r#"
10047            <body><aˇ>
10048                <script>
10049                    var x = 1;<aˇ
10050                </script>
10051            </body><aˇ>
10052        "#
10053        .unindent(),
10054    );
10055
10056    // Curly braces and parens autoclose in both HTML and JavaScript.
10057    cx.update_editor(|editor, window, cx| {
10058        editor.handle_input(" b=", window, cx);
10059        editor.handle_input("{", window, cx);
10060        editor.handle_input("c", window, cx);
10061        editor.handle_input("(", window, cx);
10062    });
10063    cx.assert_editor_state(
10064        &r#"
10065            <body><a b={c(ˇ)}>
10066                <script>
10067                    var x = 1;<a b={c(ˇ)}
10068                </script>
10069            </body><a b={c(ˇ)}>
10070        "#
10071        .unindent(),
10072    );
10073
10074    // Brackets that were already autoclosed are skipped.
10075    cx.update_editor(|editor, window, cx| {
10076        editor.handle_input(")", window, cx);
10077        editor.handle_input("d", window, cx);
10078        editor.handle_input("}", window, cx);
10079    });
10080    cx.assert_editor_state(
10081        &r#"
10082            <body><a b={c()d}ˇ>
10083                <script>
10084                    var x = 1;<a b={c()d}ˇ
10085                </script>
10086            </body><a b={c()d}ˇ>
10087        "#
10088        .unindent(),
10089    );
10090    cx.update_editor(|editor, window, cx| {
10091        editor.handle_input(">", window, cx);
10092    });
10093    cx.assert_editor_state(
10094        &r#"
10095            <body><a b={c()d}>ˇ
10096                <script>
10097                    var x = 1;<a b={c()d}>ˇ
10098                </script>
10099            </body><a b={c()d}>ˇ
10100        "#
10101        .unindent(),
10102    );
10103
10104    // Reset
10105    cx.set_state(
10106        &r#"
10107            <body>ˇ
10108                <script>
10109                    var x = 1;ˇ
10110                </script>
10111            </body>ˇ
10112        "#
10113        .unindent(),
10114    );
10115
10116    cx.update_editor(|editor, window, cx| {
10117        editor.handle_input("<", window, cx);
10118    });
10119    cx.assert_editor_state(
10120        &r#"
10121            <body><ˇ>
10122                <script>
10123                    var x = 1;<ˇ
10124                </script>
10125            </body><ˇ>
10126        "#
10127        .unindent(),
10128    );
10129
10130    // When backspacing, the closing angle brackets are removed.
10131    cx.update_editor(|editor, window, cx| {
10132        editor.backspace(&Backspace, window, cx);
10133    });
10134    cx.assert_editor_state(
10135        &r#"
10136            <body>ˇ
10137                <script>
10138                    var x = 1;ˇ
10139                </script>
10140            </body>ˇ
10141        "#
10142        .unindent(),
10143    );
10144
10145    // Block comments autoclose in JavaScript, but not HTML.
10146    cx.update_editor(|editor, window, cx| {
10147        editor.handle_input("/", window, cx);
10148        editor.handle_input("*", window, cx);
10149    });
10150    cx.assert_editor_state(
10151        &r#"
10152            <body>/*ˇ
10153                <script>
10154                    var x = 1;/*ˇ */
10155                </script>
10156            </body>/*ˇ
10157        "#
10158        .unindent(),
10159    );
10160}
10161
10162#[gpui::test]
10163async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
10164    init_test(cx, |_| {});
10165
10166    let mut cx = EditorTestContext::new(cx).await;
10167
10168    let rust_language = Arc::new(
10169        Language::new(
10170            LanguageConfig {
10171                name: "Rust".into(),
10172                brackets: serde_json::from_value(json!([
10173                    { "start": "{", "end": "}", "close": true, "newline": true },
10174                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
10175                ]))
10176                .unwrap(),
10177                autoclose_before: "})]>".into(),
10178                ..Default::default()
10179            },
10180            Some(tree_sitter_rust::LANGUAGE.into()),
10181        )
10182        .with_override_query("(string_literal) @string")
10183        .unwrap(),
10184    );
10185
10186    cx.language_registry().add(rust_language.clone());
10187    cx.update_buffer(|buffer, cx| {
10188        buffer.set_language(Some(rust_language), cx);
10189    });
10190
10191    cx.set_state(
10192        &r#"
10193            let x = ˇ
10194        "#
10195        .unindent(),
10196    );
10197
10198    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
10199    cx.update_editor(|editor, window, cx| {
10200        editor.handle_input("\"", window, cx);
10201    });
10202    cx.assert_editor_state(
10203        &r#"
10204            let x = "ˇ"
10205        "#
10206        .unindent(),
10207    );
10208
10209    // Inserting another quotation mark. The cursor moves across the existing
10210    // automatically-inserted quotation mark.
10211    cx.update_editor(|editor, window, cx| {
10212        editor.handle_input("\"", window, cx);
10213    });
10214    cx.assert_editor_state(
10215        &r#"
10216            let x = ""ˇ
10217        "#
10218        .unindent(),
10219    );
10220
10221    // Reset
10222    cx.set_state(
10223        &r#"
10224            let x = ˇ
10225        "#
10226        .unindent(),
10227    );
10228
10229    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
10230    cx.update_editor(|editor, window, cx| {
10231        editor.handle_input("\"", window, cx);
10232        editor.handle_input(" ", window, cx);
10233        editor.move_left(&Default::default(), window, cx);
10234        editor.handle_input("\\", window, cx);
10235        editor.handle_input("\"", window, cx);
10236    });
10237    cx.assert_editor_state(
10238        &r#"
10239            let x = "\"ˇ "
10240        "#
10241        .unindent(),
10242    );
10243
10244    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
10245    // mark. Nothing is inserted.
10246    cx.update_editor(|editor, window, cx| {
10247        editor.move_right(&Default::default(), window, cx);
10248        editor.handle_input("\"", window, cx);
10249    });
10250    cx.assert_editor_state(
10251        &r#"
10252            let x = "\" "ˇ
10253        "#
10254        .unindent(),
10255    );
10256}
10257
10258#[gpui::test]
10259async fn test_surround_with_pair(cx: &mut TestAppContext) {
10260    init_test(cx, |_| {});
10261
10262    let language = Arc::new(Language::new(
10263        LanguageConfig {
10264            brackets: BracketPairConfig {
10265                pairs: vec![
10266                    BracketPair {
10267                        start: "{".to_string(),
10268                        end: "}".to_string(),
10269                        close: true,
10270                        surround: true,
10271                        newline: true,
10272                    },
10273                    BracketPair {
10274                        start: "/* ".to_string(),
10275                        end: "*/".to_string(),
10276                        close: true,
10277                        surround: true,
10278                        ..Default::default()
10279                    },
10280                ],
10281                ..Default::default()
10282            },
10283            ..Default::default()
10284        },
10285        Some(tree_sitter_rust::LANGUAGE.into()),
10286    ));
10287
10288    let text = r#"
10289        a
10290        b
10291        c
10292    "#
10293    .unindent();
10294
10295    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10296    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10297    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10298    editor
10299        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10300        .await;
10301
10302    editor.update_in(cx, |editor, window, cx| {
10303        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10304            s.select_display_ranges([
10305                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10306                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10307                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
10308            ])
10309        });
10310
10311        editor.handle_input("{", window, cx);
10312        editor.handle_input("{", window, cx);
10313        editor.handle_input("{", window, cx);
10314        assert_eq!(
10315            editor.text(cx),
10316            "
10317                {{{a}}}
10318                {{{b}}}
10319                {{{c}}}
10320            "
10321            .unindent()
10322        );
10323        assert_eq!(
10324            editor.selections.display_ranges(cx),
10325            [
10326                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
10327                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
10328                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
10329            ]
10330        );
10331
10332        editor.undo(&Undo, window, cx);
10333        editor.undo(&Undo, window, cx);
10334        editor.undo(&Undo, window, cx);
10335        assert_eq!(
10336            editor.text(cx),
10337            "
10338                a
10339                b
10340                c
10341            "
10342            .unindent()
10343        );
10344        assert_eq!(
10345            editor.selections.display_ranges(cx),
10346            [
10347                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10348                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10349                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10350            ]
10351        );
10352
10353        // Ensure inserting the first character of a multi-byte bracket pair
10354        // doesn't surround the selections with the bracket.
10355        editor.handle_input("/", window, cx);
10356        assert_eq!(
10357            editor.text(cx),
10358            "
10359                /
10360                /
10361                /
10362            "
10363            .unindent()
10364        );
10365        assert_eq!(
10366            editor.selections.display_ranges(cx),
10367            [
10368                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10369                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10370                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10371            ]
10372        );
10373
10374        editor.undo(&Undo, window, cx);
10375        assert_eq!(
10376            editor.text(cx),
10377            "
10378                a
10379                b
10380                c
10381            "
10382            .unindent()
10383        );
10384        assert_eq!(
10385            editor.selections.display_ranges(cx),
10386            [
10387                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10388                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10389                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10390            ]
10391        );
10392
10393        // Ensure inserting the last character of a multi-byte bracket pair
10394        // doesn't surround the selections with the bracket.
10395        editor.handle_input("*", window, cx);
10396        assert_eq!(
10397            editor.text(cx),
10398            "
10399                *
10400                *
10401                *
10402            "
10403            .unindent()
10404        );
10405        assert_eq!(
10406            editor.selections.display_ranges(cx),
10407            [
10408                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10409                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10410                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10411            ]
10412        );
10413    });
10414}
10415
10416#[gpui::test]
10417async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
10418    init_test(cx, |_| {});
10419
10420    let language = Arc::new(Language::new(
10421        LanguageConfig {
10422            brackets: BracketPairConfig {
10423                pairs: vec![BracketPair {
10424                    start: "{".to_string(),
10425                    end: "}".to_string(),
10426                    close: true,
10427                    surround: true,
10428                    newline: true,
10429                }],
10430                ..Default::default()
10431            },
10432            autoclose_before: "}".to_string(),
10433            ..Default::default()
10434        },
10435        Some(tree_sitter_rust::LANGUAGE.into()),
10436    ));
10437
10438    let text = r#"
10439        a
10440        b
10441        c
10442    "#
10443    .unindent();
10444
10445    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10446    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10447    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10448    editor
10449        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10450        .await;
10451
10452    editor.update_in(cx, |editor, window, cx| {
10453        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10454            s.select_ranges([
10455                Point::new(0, 1)..Point::new(0, 1),
10456                Point::new(1, 1)..Point::new(1, 1),
10457                Point::new(2, 1)..Point::new(2, 1),
10458            ])
10459        });
10460
10461        editor.handle_input("{", window, cx);
10462        editor.handle_input("{", window, cx);
10463        editor.handle_input("_", window, cx);
10464        assert_eq!(
10465            editor.text(cx),
10466            "
10467                a{{_}}
10468                b{{_}}
10469                c{{_}}
10470            "
10471            .unindent()
10472        );
10473        assert_eq!(
10474            editor.selections.ranges::<Point>(cx),
10475            [
10476                Point::new(0, 4)..Point::new(0, 4),
10477                Point::new(1, 4)..Point::new(1, 4),
10478                Point::new(2, 4)..Point::new(2, 4)
10479            ]
10480        );
10481
10482        editor.backspace(&Default::default(), window, cx);
10483        editor.backspace(&Default::default(), window, cx);
10484        assert_eq!(
10485            editor.text(cx),
10486            "
10487                a{}
10488                b{}
10489                c{}
10490            "
10491            .unindent()
10492        );
10493        assert_eq!(
10494            editor.selections.ranges::<Point>(cx),
10495            [
10496                Point::new(0, 2)..Point::new(0, 2),
10497                Point::new(1, 2)..Point::new(1, 2),
10498                Point::new(2, 2)..Point::new(2, 2)
10499            ]
10500        );
10501
10502        editor.delete_to_previous_word_start(&Default::default(), window, cx);
10503        assert_eq!(
10504            editor.text(cx),
10505            "
10506                a
10507                b
10508                c
10509            "
10510            .unindent()
10511        );
10512        assert_eq!(
10513            editor.selections.ranges::<Point>(cx),
10514            [
10515                Point::new(0, 1)..Point::new(0, 1),
10516                Point::new(1, 1)..Point::new(1, 1),
10517                Point::new(2, 1)..Point::new(2, 1)
10518            ]
10519        );
10520    });
10521}
10522
10523#[gpui::test]
10524async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
10525    init_test(cx, |settings| {
10526        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
10527    });
10528
10529    let mut cx = EditorTestContext::new(cx).await;
10530
10531    let language = Arc::new(Language::new(
10532        LanguageConfig {
10533            brackets: BracketPairConfig {
10534                pairs: vec![
10535                    BracketPair {
10536                        start: "{".to_string(),
10537                        end: "}".to_string(),
10538                        close: true,
10539                        surround: true,
10540                        newline: true,
10541                    },
10542                    BracketPair {
10543                        start: "(".to_string(),
10544                        end: ")".to_string(),
10545                        close: true,
10546                        surround: true,
10547                        newline: true,
10548                    },
10549                    BracketPair {
10550                        start: "[".to_string(),
10551                        end: "]".to_string(),
10552                        close: false,
10553                        surround: true,
10554                        newline: true,
10555                    },
10556                ],
10557                ..Default::default()
10558            },
10559            autoclose_before: "})]".to_string(),
10560            ..Default::default()
10561        },
10562        Some(tree_sitter_rust::LANGUAGE.into()),
10563    ));
10564
10565    cx.language_registry().add(language.clone());
10566    cx.update_buffer(|buffer, cx| {
10567        buffer.set_language(Some(language), cx);
10568    });
10569
10570    cx.set_state(
10571        &"
10572            {(ˇ)}
10573            [[ˇ]]
10574            {(ˇ)}
10575        "
10576        .unindent(),
10577    );
10578
10579    cx.update_editor(|editor, window, cx| {
10580        editor.backspace(&Default::default(), window, cx);
10581        editor.backspace(&Default::default(), window, cx);
10582    });
10583
10584    cx.assert_editor_state(
10585        &"
10586            ˇ
10587            ˇ]]
10588            ˇ
10589        "
10590        .unindent(),
10591    );
10592
10593    cx.update_editor(|editor, window, cx| {
10594        editor.handle_input("{", window, cx);
10595        editor.handle_input("{", window, cx);
10596        editor.move_right(&MoveRight, window, cx);
10597        editor.move_right(&MoveRight, window, cx);
10598        editor.move_left(&MoveLeft, window, cx);
10599        editor.move_left(&MoveLeft, window, cx);
10600        editor.backspace(&Default::default(), window, cx);
10601    });
10602
10603    cx.assert_editor_state(
10604        &"
10605            {ˇ}
10606            {ˇ}]]
10607            {ˇ}
10608        "
10609        .unindent(),
10610    );
10611
10612    cx.update_editor(|editor, window, cx| {
10613        editor.backspace(&Default::default(), window, cx);
10614    });
10615
10616    cx.assert_editor_state(
10617        &"
10618            ˇ
10619            ˇ]]
10620            ˇ
10621        "
10622        .unindent(),
10623    );
10624}
10625
10626#[gpui::test]
10627async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
10628    init_test(cx, |_| {});
10629
10630    let language = Arc::new(Language::new(
10631        LanguageConfig::default(),
10632        Some(tree_sitter_rust::LANGUAGE.into()),
10633    ));
10634
10635    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
10636    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10637    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10638    editor
10639        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10640        .await;
10641
10642    editor.update_in(cx, |editor, window, cx| {
10643        editor.set_auto_replace_emoji_shortcode(true);
10644
10645        editor.handle_input("Hello ", window, cx);
10646        editor.handle_input(":wave", window, cx);
10647        assert_eq!(editor.text(cx), "Hello :wave".unindent());
10648
10649        editor.handle_input(":", window, cx);
10650        assert_eq!(editor.text(cx), "Hello 👋".unindent());
10651
10652        editor.handle_input(" :smile", window, cx);
10653        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
10654
10655        editor.handle_input(":", window, cx);
10656        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
10657
10658        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
10659        editor.handle_input(":wave", window, cx);
10660        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
10661
10662        editor.handle_input(":", window, cx);
10663        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
10664
10665        editor.handle_input(":1", window, cx);
10666        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
10667
10668        editor.handle_input(":", window, cx);
10669        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
10670
10671        // Ensure shortcode does not get replaced when it is part of a word
10672        editor.handle_input(" Test:wave", window, cx);
10673        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
10674
10675        editor.handle_input(":", window, cx);
10676        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
10677
10678        editor.set_auto_replace_emoji_shortcode(false);
10679
10680        // Ensure shortcode does not get replaced when auto replace is off
10681        editor.handle_input(" :wave", window, cx);
10682        assert_eq!(
10683            editor.text(cx),
10684            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
10685        );
10686
10687        editor.handle_input(":", window, cx);
10688        assert_eq!(
10689            editor.text(cx),
10690            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
10691        );
10692    });
10693}
10694
10695#[gpui::test]
10696async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
10697    init_test(cx, |_| {});
10698
10699    let (text, insertion_ranges) = marked_text_ranges(
10700        indoc! {"
10701            ˇ
10702        "},
10703        false,
10704    );
10705
10706    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
10707    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10708
10709    _ = editor.update_in(cx, |editor, window, cx| {
10710        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
10711
10712        editor
10713            .insert_snippet(&insertion_ranges, snippet, window, cx)
10714            .unwrap();
10715
10716        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
10717            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
10718            assert_eq!(editor.text(cx), expected_text);
10719            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
10720        }
10721
10722        assert(
10723            editor,
10724            cx,
10725            indoc! {"
10726            type «» =•
10727            "},
10728        );
10729
10730        assert!(editor.context_menu_visible(), "There should be a matches");
10731    });
10732}
10733
10734#[gpui::test]
10735async fn test_snippets(cx: &mut TestAppContext) {
10736    init_test(cx, |_| {});
10737
10738    let mut cx = EditorTestContext::new(cx).await;
10739
10740    cx.set_state(indoc! {"
10741        a.ˇ b
10742        a.ˇ b
10743        a.ˇ b
10744    "});
10745
10746    cx.update_editor(|editor, window, cx| {
10747        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
10748        let insertion_ranges = editor
10749            .selections
10750            .all(cx)
10751            .iter()
10752            .map(|s| s.range())
10753            .collect::<Vec<_>>();
10754        editor
10755            .insert_snippet(&insertion_ranges, snippet, window, cx)
10756            .unwrap();
10757    });
10758
10759    cx.assert_editor_state(indoc! {"
10760        a.f(«oneˇ», two, «threeˇ») b
10761        a.f(«oneˇ», two, «threeˇ») b
10762        a.f(«oneˇ», two, «threeˇ») b
10763    "});
10764
10765    // Can't move earlier than the first tab stop
10766    cx.update_editor(|editor, window, cx| {
10767        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
10768    });
10769    cx.assert_editor_state(indoc! {"
10770        a.f(«oneˇ», two, «threeˇ») b
10771        a.f(«oneˇ», two, «threeˇ») b
10772        a.f(«oneˇ», two, «threeˇ») b
10773    "});
10774
10775    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10776    cx.assert_editor_state(indoc! {"
10777        a.f(one, «twoˇ», three) b
10778        a.f(one, «twoˇ», three) b
10779        a.f(one, «twoˇ», three) b
10780    "});
10781
10782    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
10783    cx.assert_editor_state(indoc! {"
10784        a.f(«oneˇ», two, «threeˇ») b
10785        a.f(«oneˇ», two, «threeˇ») b
10786        a.f(«oneˇ», two, «threeˇ») b
10787    "});
10788
10789    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10790    cx.assert_editor_state(indoc! {"
10791        a.f(one, «twoˇ», three) b
10792        a.f(one, «twoˇ», three) b
10793        a.f(one, «twoˇ», three) b
10794    "});
10795    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10796    cx.assert_editor_state(indoc! {"
10797        a.f(one, two, three)ˇ b
10798        a.f(one, two, three)ˇ b
10799        a.f(one, two, three)ˇ b
10800    "});
10801
10802    // As soon as the last tab stop is reached, snippet state is gone
10803    cx.update_editor(|editor, window, cx| {
10804        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
10805    });
10806    cx.assert_editor_state(indoc! {"
10807        a.f(one, two, three)ˇ b
10808        a.f(one, two, three)ˇ b
10809        a.f(one, two, three)ˇ b
10810    "});
10811}
10812
10813#[gpui::test]
10814async fn test_snippet_indentation(cx: &mut TestAppContext) {
10815    init_test(cx, |_| {});
10816
10817    let mut cx = EditorTestContext::new(cx).await;
10818
10819    cx.update_editor(|editor, window, cx| {
10820        let snippet = Snippet::parse(indoc! {"
10821            /*
10822             * Multiline comment with leading indentation
10823             *
10824             * $1
10825             */
10826            $0"})
10827        .unwrap();
10828        let insertion_ranges = editor
10829            .selections
10830            .all(cx)
10831            .iter()
10832            .map(|s| s.range())
10833            .collect::<Vec<_>>();
10834        editor
10835            .insert_snippet(&insertion_ranges, snippet, window, cx)
10836            .unwrap();
10837    });
10838
10839    cx.assert_editor_state(indoc! {"
10840        /*
10841         * Multiline comment with leading indentation
10842         *
10843         * ˇ
10844         */
10845    "});
10846
10847    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10848    cx.assert_editor_state(indoc! {"
10849        /*
10850         * Multiline comment with leading indentation
10851         *
10852         *•
10853         */
10854        ˇ"});
10855}
10856
10857#[gpui::test]
10858async fn test_document_format_during_save(cx: &mut TestAppContext) {
10859    init_test(cx, |_| {});
10860
10861    let fs = FakeFs::new(cx.executor());
10862    fs.insert_file(path!("/file.rs"), Default::default()).await;
10863
10864    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
10865
10866    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10867    language_registry.add(rust_lang());
10868    let mut fake_servers = language_registry.register_fake_lsp(
10869        "Rust",
10870        FakeLspAdapter {
10871            capabilities: lsp::ServerCapabilities {
10872                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10873                ..Default::default()
10874            },
10875            ..Default::default()
10876        },
10877    );
10878
10879    let buffer = project
10880        .update(cx, |project, cx| {
10881            project.open_local_buffer(path!("/file.rs"), cx)
10882        })
10883        .await
10884        .unwrap();
10885
10886    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10887    let (editor, cx) = cx.add_window_view(|window, cx| {
10888        build_editor_with_project(project.clone(), buffer, window, cx)
10889    });
10890    editor.update_in(cx, |editor, window, cx| {
10891        editor.set_text("one\ntwo\nthree\n", window, cx)
10892    });
10893    assert!(cx.read(|cx| editor.is_dirty(cx)));
10894
10895    cx.executor().start_waiting();
10896    let fake_server = fake_servers.next().await.unwrap();
10897
10898    {
10899        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10900            move |params, _| async move {
10901                assert_eq!(
10902                    params.text_document.uri,
10903                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
10904                );
10905                assert_eq!(params.options.tab_size, 4);
10906                Ok(Some(vec![lsp::TextEdit::new(
10907                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10908                    ", ".to_string(),
10909                )]))
10910            },
10911        );
10912        let save = editor
10913            .update_in(cx, |editor, window, cx| {
10914                editor.save(
10915                    SaveOptions {
10916                        format: true,
10917                        autosave: false,
10918                    },
10919                    project.clone(),
10920                    window,
10921                    cx,
10922                )
10923            })
10924            .unwrap();
10925        cx.executor().start_waiting();
10926        save.await;
10927
10928        assert_eq!(
10929            editor.update(cx, |editor, cx| editor.text(cx)),
10930            "one, two\nthree\n"
10931        );
10932        assert!(!cx.read(|cx| editor.is_dirty(cx)));
10933    }
10934
10935    {
10936        editor.update_in(cx, |editor, window, cx| {
10937            editor.set_text("one\ntwo\nthree\n", window, cx)
10938        });
10939        assert!(cx.read(|cx| editor.is_dirty(cx)));
10940
10941        // Ensure we can still save even if formatting hangs.
10942        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10943            move |params, _| async move {
10944                assert_eq!(
10945                    params.text_document.uri,
10946                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
10947                );
10948                futures::future::pending::<()>().await;
10949                unreachable!()
10950            },
10951        );
10952        let save = editor
10953            .update_in(cx, |editor, window, cx| {
10954                editor.save(
10955                    SaveOptions {
10956                        format: true,
10957                        autosave: false,
10958                    },
10959                    project.clone(),
10960                    window,
10961                    cx,
10962                )
10963            })
10964            .unwrap();
10965        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
10966        cx.executor().start_waiting();
10967        save.await;
10968        assert_eq!(
10969            editor.update(cx, |editor, cx| editor.text(cx)),
10970            "one\ntwo\nthree\n"
10971        );
10972    }
10973
10974    // Set rust language override and assert overridden tabsize is sent to language server
10975    update_test_language_settings(cx, |settings| {
10976        settings.languages.0.insert(
10977            "Rust".into(),
10978            LanguageSettingsContent {
10979                tab_size: NonZeroU32::new(8),
10980                ..Default::default()
10981            },
10982        );
10983    });
10984
10985    {
10986        editor.update_in(cx, |editor, window, cx| {
10987            editor.set_text("somehting_new\n", window, cx)
10988        });
10989        assert!(cx.read(|cx| editor.is_dirty(cx)));
10990        let _formatting_request_signal = fake_server
10991            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
10992                assert_eq!(
10993                    params.text_document.uri,
10994                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
10995                );
10996                assert_eq!(params.options.tab_size, 8);
10997                Ok(Some(vec![]))
10998            });
10999        let save = editor
11000            .update_in(cx, |editor, window, cx| {
11001                editor.save(
11002                    SaveOptions {
11003                        format: true,
11004                        autosave: false,
11005                    },
11006                    project.clone(),
11007                    window,
11008                    cx,
11009                )
11010            })
11011            .unwrap();
11012        cx.executor().start_waiting();
11013        save.await;
11014    }
11015}
11016
11017#[gpui::test]
11018async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
11019    init_test(cx, |settings| {
11020        settings.defaults.ensure_final_newline_on_save = Some(false);
11021    });
11022
11023    let fs = FakeFs::new(cx.executor());
11024    fs.insert_file(path!("/file.txt"), "foo".into()).await;
11025
11026    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
11027
11028    let buffer = project
11029        .update(cx, |project, cx| {
11030            project.open_local_buffer(path!("/file.txt"), cx)
11031        })
11032        .await
11033        .unwrap();
11034
11035    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11036    let (editor, cx) = cx.add_window_view(|window, cx| {
11037        build_editor_with_project(project.clone(), buffer, window, cx)
11038    });
11039    editor.update_in(cx, |editor, window, cx| {
11040        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
11041            s.select_ranges([0..0])
11042        });
11043    });
11044    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11045
11046    editor.update_in(cx, |editor, window, cx| {
11047        editor.handle_input("\n", window, cx)
11048    });
11049    cx.run_until_parked();
11050    save(&editor, &project, cx).await;
11051    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11052
11053    editor.update_in(cx, |editor, window, cx| {
11054        editor.undo(&Default::default(), window, cx);
11055    });
11056    save(&editor, &project, cx).await;
11057    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11058
11059    editor.update_in(cx, |editor, window, cx| {
11060        editor.redo(&Default::default(), window, cx);
11061    });
11062    cx.run_until_parked();
11063    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11064
11065    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
11066        let save = editor
11067            .update_in(cx, |editor, window, cx| {
11068                editor.save(
11069                    SaveOptions {
11070                        format: true,
11071                        autosave: false,
11072                    },
11073                    project.clone(),
11074                    window,
11075                    cx,
11076                )
11077            })
11078            .unwrap();
11079        cx.executor().start_waiting();
11080        save.await;
11081        assert!(!cx.read(|cx| editor.is_dirty(cx)));
11082    }
11083}
11084
11085#[gpui::test]
11086async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
11087    init_test(cx, |_| {});
11088
11089    let cols = 4;
11090    let rows = 10;
11091    let sample_text_1 = sample_text(rows, cols, 'a');
11092    assert_eq!(
11093        sample_text_1,
11094        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11095    );
11096    let sample_text_2 = sample_text(rows, cols, 'l');
11097    assert_eq!(
11098        sample_text_2,
11099        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11100    );
11101    let sample_text_3 = sample_text(rows, cols, 'v');
11102    assert_eq!(
11103        sample_text_3,
11104        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11105    );
11106
11107    let fs = FakeFs::new(cx.executor());
11108    fs.insert_tree(
11109        path!("/a"),
11110        json!({
11111            "main.rs": sample_text_1,
11112            "other.rs": sample_text_2,
11113            "lib.rs": sample_text_3,
11114        }),
11115    )
11116    .await;
11117
11118    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11119    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11120    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11121
11122    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11123    language_registry.add(rust_lang());
11124    let mut fake_servers = language_registry.register_fake_lsp(
11125        "Rust",
11126        FakeLspAdapter {
11127            capabilities: lsp::ServerCapabilities {
11128                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11129                ..Default::default()
11130            },
11131            ..Default::default()
11132        },
11133    );
11134
11135    let worktree = project.update(cx, |project, cx| {
11136        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
11137        assert_eq!(worktrees.len(), 1);
11138        worktrees.pop().unwrap()
11139    });
11140    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11141
11142    let buffer_1 = project
11143        .update(cx, |project, cx| {
11144            project.open_buffer((worktree_id, "main.rs"), cx)
11145        })
11146        .await
11147        .unwrap();
11148    let buffer_2 = project
11149        .update(cx, |project, cx| {
11150            project.open_buffer((worktree_id, "other.rs"), cx)
11151        })
11152        .await
11153        .unwrap();
11154    let buffer_3 = project
11155        .update(cx, |project, cx| {
11156            project.open_buffer((worktree_id, "lib.rs"), cx)
11157        })
11158        .await
11159        .unwrap();
11160
11161    let multi_buffer = cx.new(|cx| {
11162        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11163        multi_buffer.push_excerpts(
11164            buffer_1.clone(),
11165            [
11166                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11167                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11168                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11169            ],
11170            cx,
11171        );
11172        multi_buffer.push_excerpts(
11173            buffer_2.clone(),
11174            [
11175                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11176                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11177                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11178            ],
11179            cx,
11180        );
11181        multi_buffer.push_excerpts(
11182            buffer_3.clone(),
11183            [
11184                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11185                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11186                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11187            ],
11188            cx,
11189        );
11190        multi_buffer
11191    });
11192    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
11193        Editor::new(
11194            EditorMode::full(),
11195            multi_buffer,
11196            Some(project.clone()),
11197            window,
11198            cx,
11199        )
11200    });
11201
11202    multi_buffer_editor.update_in(cx, |editor, window, cx| {
11203        editor.change_selections(
11204            SelectionEffects::scroll(Autoscroll::Next),
11205            window,
11206            cx,
11207            |s| s.select_ranges(Some(1..2)),
11208        );
11209        editor.insert("|one|two|three|", window, cx);
11210    });
11211    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
11212    multi_buffer_editor.update_in(cx, |editor, window, cx| {
11213        editor.change_selections(
11214            SelectionEffects::scroll(Autoscroll::Next),
11215            window,
11216            cx,
11217            |s| s.select_ranges(Some(60..70)),
11218        );
11219        editor.insert("|four|five|six|", window, cx);
11220    });
11221    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
11222
11223    // First two buffers should be edited, but not the third one.
11224    assert_eq!(
11225        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11226        "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}",
11227    );
11228    buffer_1.update(cx, |buffer, _| {
11229        assert!(buffer.is_dirty());
11230        assert_eq!(
11231            buffer.text(),
11232            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
11233        )
11234    });
11235    buffer_2.update(cx, |buffer, _| {
11236        assert!(buffer.is_dirty());
11237        assert_eq!(
11238            buffer.text(),
11239            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
11240        )
11241    });
11242    buffer_3.update(cx, |buffer, _| {
11243        assert!(!buffer.is_dirty());
11244        assert_eq!(buffer.text(), sample_text_3,)
11245    });
11246    cx.executor().run_until_parked();
11247
11248    cx.executor().start_waiting();
11249    let save = multi_buffer_editor
11250        .update_in(cx, |editor, window, cx| {
11251            editor.save(
11252                SaveOptions {
11253                    format: true,
11254                    autosave: false,
11255                },
11256                project.clone(),
11257                window,
11258                cx,
11259            )
11260        })
11261        .unwrap();
11262
11263    let fake_server = fake_servers.next().await.unwrap();
11264    fake_server
11265        .server
11266        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
11267            Ok(Some(vec![lsp::TextEdit::new(
11268                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11269                format!("[{} formatted]", params.text_document.uri),
11270            )]))
11271        })
11272        .detach();
11273    save.await;
11274
11275    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
11276    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
11277    assert_eq!(
11278        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11279        uri!(
11280            "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}"
11281        ),
11282    );
11283    buffer_1.update(cx, |buffer, _| {
11284        assert!(!buffer.is_dirty());
11285        assert_eq!(
11286            buffer.text(),
11287            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
11288        )
11289    });
11290    buffer_2.update(cx, |buffer, _| {
11291        assert!(!buffer.is_dirty());
11292        assert_eq!(
11293            buffer.text(),
11294            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
11295        )
11296    });
11297    buffer_3.update(cx, |buffer, _| {
11298        assert!(!buffer.is_dirty());
11299        assert_eq!(buffer.text(), sample_text_3,)
11300    });
11301}
11302
11303#[gpui::test]
11304async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
11305    init_test(cx, |_| {});
11306
11307    let fs = FakeFs::new(cx.executor());
11308    fs.insert_tree(
11309        path!("/dir"),
11310        json!({
11311            "file1.rs": "fn main() { println!(\"hello\"); }",
11312            "file2.rs": "fn test() { println!(\"test\"); }",
11313            "file3.rs": "fn other() { println!(\"other\"); }\n",
11314        }),
11315    )
11316    .await;
11317
11318    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
11319    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11320    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11321
11322    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11323    language_registry.add(rust_lang());
11324
11325    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
11326    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11327
11328    // Open three buffers
11329    let buffer_1 = project
11330        .update(cx, |project, cx| {
11331            project.open_buffer((worktree_id, "file1.rs"), cx)
11332        })
11333        .await
11334        .unwrap();
11335    let buffer_2 = project
11336        .update(cx, |project, cx| {
11337            project.open_buffer((worktree_id, "file2.rs"), cx)
11338        })
11339        .await
11340        .unwrap();
11341    let buffer_3 = project
11342        .update(cx, |project, cx| {
11343            project.open_buffer((worktree_id, "file3.rs"), cx)
11344        })
11345        .await
11346        .unwrap();
11347
11348    // Create a multi-buffer with all three buffers
11349    let multi_buffer = cx.new(|cx| {
11350        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11351        multi_buffer.push_excerpts(
11352            buffer_1.clone(),
11353            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11354            cx,
11355        );
11356        multi_buffer.push_excerpts(
11357            buffer_2.clone(),
11358            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11359            cx,
11360        );
11361        multi_buffer.push_excerpts(
11362            buffer_3.clone(),
11363            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11364            cx,
11365        );
11366        multi_buffer
11367    });
11368
11369    let editor = cx.new_window_entity(|window, cx| {
11370        Editor::new(
11371            EditorMode::full(),
11372            multi_buffer,
11373            Some(project.clone()),
11374            window,
11375            cx,
11376        )
11377    });
11378
11379    // Edit only the first buffer
11380    editor.update_in(cx, |editor, window, cx| {
11381        editor.change_selections(
11382            SelectionEffects::scroll(Autoscroll::Next),
11383            window,
11384            cx,
11385            |s| s.select_ranges(Some(10..10)),
11386        );
11387        editor.insert("// edited", window, cx);
11388    });
11389
11390    // Verify that only buffer 1 is dirty
11391    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
11392    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11393    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11394
11395    // Get write counts after file creation (files were created with initial content)
11396    // We expect each file to have been written once during creation
11397    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
11398    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
11399    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
11400
11401    // Perform autosave
11402    let save_task = editor.update_in(cx, |editor, window, cx| {
11403        editor.save(
11404            SaveOptions {
11405                format: true,
11406                autosave: true,
11407            },
11408            project.clone(),
11409            window,
11410            cx,
11411        )
11412    });
11413    save_task.await.unwrap();
11414
11415    // Only the dirty buffer should have been saved
11416    assert_eq!(
11417        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
11418        1,
11419        "Buffer 1 was dirty, so it should have been written once during autosave"
11420    );
11421    assert_eq!(
11422        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
11423        0,
11424        "Buffer 2 was clean, so it should not have been written during autosave"
11425    );
11426    assert_eq!(
11427        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
11428        0,
11429        "Buffer 3 was clean, so it should not have been written during autosave"
11430    );
11431
11432    // Verify buffer states after autosave
11433    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11434    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11435    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11436
11437    // Now perform a manual save (format = true)
11438    let save_task = editor.update_in(cx, |editor, window, cx| {
11439        editor.save(
11440            SaveOptions {
11441                format: true,
11442                autosave: false,
11443            },
11444            project.clone(),
11445            window,
11446            cx,
11447        )
11448    });
11449    save_task.await.unwrap();
11450
11451    // During manual save, clean buffers don't get written to disk
11452    // They just get did_save called for language server notifications
11453    assert_eq!(
11454        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
11455        1,
11456        "Buffer 1 should only have been written once total (during autosave, not manual save)"
11457    );
11458    assert_eq!(
11459        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
11460        0,
11461        "Buffer 2 should not have been written at all"
11462    );
11463    assert_eq!(
11464        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
11465        0,
11466        "Buffer 3 should not have been written at all"
11467    );
11468}
11469
11470async fn setup_range_format_test(
11471    cx: &mut TestAppContext,
11472) -> (
11473    Entity<Project>,
11474    Entity<Editor>,
11475    &mut gpui::VisualTestContext,
11476    lsp::FakeLanguageServer,
11477) {
11478    init_test(cx, |_| {});
11479
11480    let fs = FakeFs::new(cx.executor());
11481    fs.insert_file(path!("/file.rs"), Default::default()).await;
11482
11483    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11484
11485    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11486    language_registry.add(rust_lang());
11487    let mut fake_servers = language_registry.register_fake_lsp(
11488        "Rust",
11489        FakeLspAdapter {
11490            capabilities: lsp::ServerCapabilities {
11491                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
11492                ..lsp::ServerCapabilities::default()
11493            },
11494            ..FakeLspAdapter::default()
11495        },
11496    );
11497
11498    let buffer = project
11499        .update(cx, |project, cx| {
11500            project.open_local_buffer(path!("/file.rs"), cx)
11501        })
11502        .await
11503        .unwrap();
11504
11505    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11506    let (editor, cx) = cx.add_window_view(|window, cx| {
11507        build_editor_with_project(project.clone(), buffer, window, cx)
11508    });
11509
11510    cx.executor().start_waiting();
11511    let fake_server = fake_servers.next().await.unwrap();
11512
11513    (project, editor, cx, fake_server)
11514}
11515
11516#[gpui::test]
11517async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
11518    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11519
11520    editor.update_in(cx, |editor, window, cx| {
11521        editor.set_text("one\ntwo\nthree\n", window, cx)
11522    });
11523    assert!(cx.read(|cx| editor.is_dirty(cx)));
11524
11525    let save = editor
11526        .update_in(cx, |editor, window, cx| {
11527            editor.save(
11528                SaveOptions {
11529                    format: true,
11530                    autosave: false,
11531                },
11532                project.clone(),
11533                window,
11534                cx,
11535            )
11536        })
11537        .unwrap();
11538    fake_server
11539        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
11540            assert_eq!(
11541                params.text_document.uri,
11542                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11543            );
11544            assert_eq!(params.options.tab_size, 4);
11545            Ok(Some(vec![lsp::TextEdit::new(
11546                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11547                ", ".to_string(),
11548            )]))
11549        })
11550        .next()
11551        .await;
11552    cx.executor().start_waiting();
11553    save.await;
11554    assert_eq!(
11555        editor.update(cx, |editor, cx| editor.text(cx)),
11556        "one, two\nthree\n"
11557    );
11558    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11559}
11560
11561#[gpui::test]
11562async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
11563    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11564
11565    editor.update_in(cx, |editor, window, cx| {
11566        editor.set_text("one\ntwo\nthree\n", window, cx)
11567    });
11568    assert!(cx.read(|cx| editor.is_dirty(cx)));
11569
11570    // Test that save still works when formatting hangs
11571    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
11572        move |params, _| async move {
11573            assert_eq!(
11574                params.text_document.uri,
11575                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11576            );
11577            futures::future::pending::<()>().await;
11578            unreachable!()
11579        },
11580    );
11581    let save = editor
11582        .update_in(cx, |editor, window, cx| {
11583            editor.save(
11584                SaveOptions {
11585                    format: true,
11586                    autosave: false,
11587                },
11588                project.clone(),
11589                window,
11590                cx,
11591            )
11592        })
11593        .unwrap();
11594    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11595    cx.executor().start_waiting();
11596    save.await;
11597    assert_eq!(
11598        editor.update(cx, |editor, cx| editor.text(cx)),
11599        "one\ntwo\nthree\n"
11600    );
11601    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11602}
11603
11604#[gpui::test]
11605async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
11606    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11607
11608    // Buffer starts clean, no formatting should be requested
11609    let save = editor
11610        .update_in(cx, |editor, window, cx| {
11611            editor.save(
11612                SaveOptions {
11613                    format: false,
11614                    autosave: false,
11615                },
11616                project.clone(),
11617                window,
11618                cx,
11619            )
11620        })
11621        .unwrap();
11622    let _pending_format_request = fake_server
11623        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
11624            panic!("Should not be invoked");
11625        })
11626        .next();
11627    cx.executor().start_waiting();
11628    save.await;
11629    cx.run_until_parked();
11630}
11631
11632#[gpui::test]
11633async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
11634    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11635
11636    // Set Rust language override and assert overridden tabsize is sent to language server
11637    update_test_language_settings(cx, |settings| {
11638        settings.languages.0.insert(
11639            "Rust".into(),
11640            LanguageSettingsContent {
11641                tab_size: NonZeroU32::new(8),
11642                ..Default::default()
11643            },
11644        );
11645    });
11646
11647    editor.update_in(cx, |editor, window, cx| {
11648        editor.set_text("something_new\n", window, cx)
11649    });
11650    assert!(cx.read(|cx| editor.is_dirty(cx)));
11651    let save = editor
11652        .update_in(cx, |editor, window, cx| {
11653            editor.save(
11654                SaveOptions {
11655                    format: true,
11656                    autosave: false,
11657                },
11658                project.clone(),
11659                window,
11660                cx,
11661            )
11662        })
11663        .unwrap();
11664    fake_server
11665        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
11666            assert_eq!(
11667                params.text_document.uri,
11668                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11669            );
11670            assert_eq!(params.options.tab_size, 8);
11671            Ok(Some(Vec::new()))
11672        })
11673        .next()
11674        .await;
11675    save.await;
11676}
11677
11678#[gpui::test]
11679async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
11680    init_test(cx, |settings| {
11681        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
11682            Formatter::LanguageServer { name: None },
11683        )))
11684    });
11685
11686    let fs = FakeFs::new(cx.executor());
11687    fs.insert_file(path!("/file.rs"), Default::default()).await;
11688
11689    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11690
11691    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11692    language_registry.add(Arc::new(Language::new(
11693        LanguageConfig {
11694            name: "Rust".into(),
11695            matcher: LanguageMatcher {
11696                path_suffixes: vec!["rs".to_string()],
11697                ..Default::default()
11698            },
11699            ..LanguageConfig::default()
11700        },
11701        Some(tree_sitter_rust::LANGUAGE.into()),
11702    )));
11703    update_test_language_settings(cx, |settings| {
11704        // Enable Prettier formatting for the same buffer, and ensure
11705        // LSP is called instead of Prettier.
11706        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
11707    });
11708    let mut fake_servers = language_registry.register_fake_lsp(
11709        "Rust",
11710        FakeLspAdapter {
11711            capabilities: lsp::ServerCapabilities {
11712                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11713                ..Default::default()
11714            },
11715            ..Default::default()
11716        },
11717    );
11718
11719    let buffer = project
11720        .update(cx, |project, cx| {
11721            project.open_local_buffer(path!("/file.rs"), cx)
11722        })
11723        .await
11724        .unwrap();
11725
11726    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11727    let (editor, cx) = cx.add_window_view(|window, cx| {
11728        build_editor_with_project(project.clone(), buffer, window, cx)
11729    });
11730    editor.update_in(cx, |editor, window, cx| {
11731        editor.set_text("one\ntwo\nthree\n", window, cx)
11732    });
11733
11734    cx.executor().start_waiting();
11735    let fake_server = fake_servers.next().await.unwrap();
11736
11737    let format = editor
11738        .update_in(cx, |editor, window, cx| {
11739            editor.perform_format(
11740                project.clone(),
11741                FormatTrigger::Manual,
11742                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
11743                window,
11744                cx,
11745            )
11746        })
11747        .unwrap();
11748    fake_server
11749        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
11750            assert_eq!(
11751                params.text_document.uri,
11752                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11753            );
11754            assert_eq!(params.options.tab_size, 4);
11755            Ok(Some(vec![lsp::TextEdit::new(
11756                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11757                ", ".to_string(),
11758            )]))
11759        })
11760        .next()
11761        .await;
11762    cx.executor().start_waiting();
11763    format.await;
11764    assert_eq!(
11765        editor.update(cx, |editor, cx| editor.text(cx)),
11766        "one, two\nthree\n"
11767    );
11768
11769    editor.update_in(cx, |editor, window, cx| {
11770        editor.set_text("one\ntwo\nthree\n", window, cx)
11771    });
11772    // Ensure we don't lock if formatting hangs.
11773    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11774        move |params, _| async move {
11775            assert_eq!(
11776                params.text_document.uri,
11777                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11778            );
11779            futures::future::pending::<()>().await;
11780            unreachable!()
11781        },
11782    );
11783    let format = editor
11784        .update_in(cx, |editor, window, cx| {
11785            editor.perform_format(
11786                project,
11787                FormatTrigger::Manual,
11788                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
11789                window,
11790                cx,
11791            )
11792        })
11793        .unwrap();
11794    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11795    cx.executor().start_waiting();
11796    format.await;
11797    assert_eq!(
11798        editor.update(cx, |editor, cx| editor.text(cx)),
11799        "one\ntwo\nthree\n"
11800    );
11801}
11802
11803#[gpui::test]
11804async fn test_multiple_formatters(cx: &mut TestAppContext) {
11805    init_test(cx, |settings| {
11806        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
11807        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
11808            Formatter::LanguageServer { name: None },
11809            Formatter::CodeActions(
11810                [
11811                    ("code-action-1".into(), true),
11812                    ("code-action-2".into(), true),
11813                ]
11814                .into_iter()
11815                .collect(),
11816            ),
11817        ])))
11818    });
11819
11820    let fs = FakeFs::new(cx.executor());
11821    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
11822        .await;
11823
11824    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11825    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11826    language_registry.add(rust_lang());
11827
11828    let mut fake_servers = language_registry.register_fake_lsp(
11829        "Rust",
11830        FakeLspAdapter {
11831            capabilities: lsp::ServerCapabilities {
11832                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11833                execute_command_provider: Some(lsp::ExecuteCommandOptions {
11834                    commands: vec!["the-command-for-code-action-1".into()],
11835                    ..Default::default()
11836                }),
11837                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
11838                ..Default::default()
11839            },
11840            ..Default::default()
11841        },
11842    );
11843
11844    let buffer = project
11845        .update(cx, |project, cx| {
11846            project.open_local_buffer(path!("/file.rs"), cx)
11847        })
11848        .await
11849        .unwrap();
11850
11851    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11852    let (editor, cx) = cx.add_window_view(|window, cx| {
11853        build_editor_with_project(project.clone(), buffer, window, cx)
11854    });
11855
11856    cx.executor().start_waiting();
11857
11858    let fake_server = fake_servers.next().await.unwrap();
11859    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11860        move |_params, _| async move {
11861            Ok(Some(vec![lsp::TextEdit::new(
11862                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
11863                "applied-formatting\n".to_string(),
11864            )]))
11865        },
11866    );
11867    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
11868        move |params, _| async move {
11869            assert_eq!(
11870                params.context.only,
11871                Some(vec!["code-action-1".into(), "code-action-2".into()])
11872            );
11873            let uri = lsp::Uri::from_file_path(path!("/file.rs")).unwrap();
11874            Ok(Some(vec![
11875                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
11876                    kind: Some("code-action-1".into()),
11877                    edit: Some(lsp::WorkspaceEdit::new(
11878                        [(
11879                            uri.clone(),
11880                            vec![lsp::TextEdit::new(
11881                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
11882                                "applied-code-action-1-edit\n".to_string(),
11883                            )],
11884                        )]
11885                        .into_iter()
11886                        .collect(),
11887                    )),
11888                    command: Some(lsp::Command {
11889                        command: "the-command-for-code-action-1".into(),
11890                        ..Default::default()
11891                    }),
11892                    ..Default::default()
11893                }),
11894                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
11895                    kind: Some("code-action-2".into()),
11896                    edit: Some(lsp::WorkspaceEdit::new(
11897                        [(
11898                            uri,
11899                            vec![lsp::TextEdit::new(
11900                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
11901                                "applied-code-action-2-edit\n".to_string(),
11902                            )],
11903                        )]
11904                        .into_iter()
11905                        .collect(),
11906                    )),
11907                    ..Default::default()
11908                }),
11909            ]))
11910        },
11911    );
11912
11913    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
11914        move |params, _| async move { Ok(params) }
11915    });
11916
11917    let command_lock = Arc::new(futures::lock::Mutex::new(()));
11918    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
11919        let fake = fake_server.clone();
11920        let lock = command_lock.clone();
11921        move |params, _| {
11922            assert_eq!(params.command, "the-command-for-code-action-1");
11923            let fake = fake.clone();
11924            let lock = lock.clone();
11925            async move {
11926                lock.lock().await;
11927                fake.server
11928                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
11929                        label: None,
11930                        edit: lsp::WorkspaceEdit {
11931                            changes: Some(
11932                                [(
11933                                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
11934                                    vec![lsp::TextEdit {
11935                                        range: lsp::Range::new(
11936                                            lsp::Position::new(0, 0),
11937                                            lsp::Position::new(0, 0),
11938                                        ),
11939                                        new_text: "applied-code-action-1-command\n".into(),
11940                                    }],
11941                                )]
11942                                .into_iter()
11943                                .collect(),
11944                            ),
11945                            ..Default::default()
11946                        },
11947                    })
11948                    .await
11949                    .into_response()
11950                    .unwrap();
11951                Ok(Some(json!(null)))
11952            }
11953        }
11954    });
11955
11956    cx.executor().start_waiting();
11957    editor
11958        .update_in(cx, |editor, window, cx| {
11959            editor.perform_format(
11960                project.clone(),
11961                FormatTrigger::Manual,
11962                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
11963                window,
11964                cx,
11965            )
11966        })
11967        .unwrap()
11968        .await;
11969    editor.update(cx, |editor, cx| {
11970        assert_eq!(
11971            editor.text(cx),
11972            r#"
11973                applied-code-action-2-edit
11974                applied-code-action-1-command
11975                applied-code-action-1-edit
11976                applied-formatting
11977                one
11978                two
11979                three
11980            "#
11981            .unindent()
11982        );
11983    });
11984
11985    editor.update_in(cx, |editor, window, cx| {
11986        editor.undo(&Default::default(), window, cx);
11987        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
11988    });
11989
11990    // Perform a manual edit while waiting for an LSP command
11991    // that's being run as part of a formatting code action.
11992    let lock_guard = command_lock.lock().await;
11993    let format = editor
11994        .update_in(cx, |editor, window, cx| {
11995            editor.perform_format(
11996                project.clone(),
11997                FormatTrigger::Manual,
11998                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
11999                window,
12000                cx,
12001            )
12002        })
12003        .unwrap();
12004    cx.run_until_parked();
12005    editor.update(cx, |editor, cx| {
12006        assert_eq!(
12007            editor.text(cx),
12008            r#"
12009                applied-code-action-1-edit
12010                applied-formatting
12011                one
12012                two
12013                three
12014            "#
12015            .unindent()
12016        );
12017
12018        editor.buffer.update(cx, |buffer, cx| {
12019            let ix = buffer.len(cx);
12020            buffer.edit([(ix..ix, "edited\n")], None, cx);
12021        });
12022    });
12023
12024    // Allow the LSP command to proceed. Because the buffer was edited,
12025    // the second code action will not be run.
12026    drop(lock_guard);
12027    format.await;
12028    editor.update_in(cx, |editor, window, cx| {
12029        assert_eq!(
12030            editor.text(cx),
12031            r#"
12032                applied-code-action-1-command
12033                applied-code-action-1-edit
12034                applied-formatting
12035                one
12036                two
12037                three
12038                edited
12039            "#
12040            .unindent()
12041        );
12042
12043        // The manual edit is undone first, because it is the last thing the user did
12044        // (even though the command completed afterwards).
12045        editor.undo(&Default::default(), window, cx);
12046        assert_eq!(
12047            editor.text(cx),
12048            r#"
12049                applied-code-action-1-command
12050                applied-code-action-1-edit
12051                applied-formatting
12052                one
12053                two
12054                three
12055            "#
12056            .unindent()
12057        );
12058
12059        // All the formatting (including the command, which completed after the manual edit)
12060        // is undone together.
12061        editor.undo(&Default::default(), window, cx);
12062        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12063    });
12064}
12065
12066#[gpui::test]
12067async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
12068    init_test(cx, |settings| {
12069        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
12070            Formatter::LanguageServer { name: None },
12071        ])))
12072    });
12073
12074    let fs = FakeFs::new(cx.executor());
12075    fs.insert_file(path!("/file.ts"), Default::default()).await;
12076
12077    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12078
12079    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12080    language_registry.add(Arc::new(Language::new(
12081        LanguageConfig {
12082            name: "TypeScript".into(),
12083            matcher: LanguageMatcher {
12084                path_suffixes: vec!["ts".to_string()],
12085                ..Default::default()
12086            },
12087            ..LanguageConfig::default()
12088        },
12089        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12090    )));
12091    update_test_language_settings(cx, |settings| {
12092        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
12093    });
12094    let mut fake_servers = language_registry.register_fake_lsp(
12095        "TypeScript",
12096        FakeLspAdapter {
12097            capabilities: lsp::ServerCapabilities {
12098                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
12099                ..Default::default()
12100            },
12101            ..Default::default()
12102        },
12103    );
12104
12105    let buffer = project
12106        .update(cx, |project, cx| {
12107            project.open_local_buffer(path!("/file.ts"), cx)
12108        })
12109        .await
12110        .unwrap();
12111
12112    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12113    let (editor, cx) = cx.add_window_view(|window, cx| {
12114        build_editor_with_project(project.clone(), buffer, window, cx)
12115    });
12116    editor.update_in(cx, |editor, window, cx| {
12117        editor.set_text(
12118            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12119            window,
12120            cx,
12121        )
12122    });
12123
12124    cx.executor().start_waiting();
12125    let fake_server = fake_servers.next().await.unwrap();
12126
12127    let format = editor
12128        .update_in(cx, |editor, window, cx| {
12129            editor.perform_code_action_kind(
12130                project.clone(),
12131                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12132                window,
12133                cx,
12134            )
12135        })
12136        .unwrap();
12137    fake_server
12138        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
12139            assert_eq!(
12140                params.text_document.uri,
12141                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12142            );
12143            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
12144                lsp::CodeAction {
12145                    title: "Organize Imports".to_string(),
12146                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
12147                    edit: Some(lsp::WorkspaceEdit {
12148                        changes: Some(
12149                            [(
12150                                params.text_document.uri.clone(),
12151                                vec![lsp::TextEdit::new(
12152                                    lsp::Range::new(
12153                                        lsp::Position::new(1, 0),
12154                                        lsp::Position::new(2, 0),
12155                                    ),
12156                                    "".to_string(),
12157                                )],
12158                            )]
12159                            .into_iter()
12160                            .collect(),
12161                        ),
12162                        ..Default::default()
12163                    }),
12164                    ..Default::default()
12165                },
12166            )]))
12167        })
12168        .next()
12169        .await;
12170    cx.executor().start_waiting();
12171    format.await;
12172    assert_eq!(
12173        editor.update(cx, |editor, cx| editor.text(cx)),
12174        "import { a } from 'module';\n\nconst x = a;\n"
12175    );
12176
12177    editor.update_in(cx, |editor, window, cx| {
12178        editor.set_text(
12179            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12180            window,
12181            cx,
12182        )
12183    });
12184    // Ensure we don't lock if code action hangs.
12185    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
12186        move |params, _| async move {
12187            assert_eq!(
12188                params.text_document.uri,
12189                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12190            );
12191            futures::future::pending::<()>().await;
12192            unreachable!()
12193        },
12194    );
12195    let format = editor
12196        .update_in(cx, |editor, window, cx| {
12197            editor.perform_code_action_kind(
12198                project,
12199                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12200                window,
12201                cx,
12202            )
12203        })
12204        .unwrap();
12205    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
12206    cx.executor().start_waiting();
12207    format.await;
12208    assert_eq!(
12209        editor.update(cx, |editor, cx| editor.text(cx)),
12210        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
12211    );
12212}
12213
12214#[gpui::test]
12215async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
12216    init_test(cx, |_| {});
12217
12218    let mut cx = EditorLspTestContext::new_rust(
12219        lsp::ServerCapabilities {
12220            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12221            ..Default::default()
12222        },
12223        cx,
12224    )
12225    .await;
12226
12227    cx.set_state(indoc! {"
12228        one.twoˇ
12229    "});
12230
12231    // The format request takes a long time. When it completes, it inserts
12232    // a newline and an indent before the `.`
12233    cx.lsp
12234        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
12235            let executor = cx.background_executor().clone();
12236            async move {
12237                executor.timer(Duration::from_millis(100)).await;
12238                Ok(Some(vec![lsp::TextEdit {
12239                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
12240                    new_text: "\n    ".into(),
12241                }]))
12242            }
12243        });
12244
12245    // Submit a format request.
12246    let format_1 = cx
12247        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12248        .unwrap();
12249    cx.executor().run_until_parked();
12250
12251    // Submit a second format request.
12252    let format_2 = cx
12253        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12254        .unwrap();
12255    cx.executor().run_until_parked();
12256
12257    // Wait for both format requests to complete
12258    cx.executor().advance_clock(Duration::from_millis(200));
12259    cx.executor().start_waiting();
12260    format_1.await.unwrap();
12261    cx.executor().start_waiting();
12262    format_2.await.unwrap();
12263
12264    // The formatting edits only happens once.
12265    cx.assert_editor_state(indoc! {"
12266        one
12267            .twoˇ
12268    "});
12269}
12270
12271#[gpui::test]
12272async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
12273    init_test(cx, |settings| {
12274        settings.defaults.formatter = Some(SelectedFormatter::Auto)
12275    });
12276
12277    let mut cx = EditorLspTestContext::new_rust(
12278        lsp::ServerCapabilities {
12279            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12280            ..Default::default()
12281        },
12282        cx,
12283    )
12284    .await;
12285
12286    // Set up a buffer white some trailing whitespace and no trailing newline.
12287    cx.set_state(
12288        &[
12289            "one ",   //
12290            "twoˇ",   //
12291            "three ", //
12292            "four",   //
12293        ]
12294        .join("\n"),
12295    );
12296
12297    // Submit a format request.
12298    let format = cx
12299        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12300        .unwrap();
12301
12302    // Record which buffer changes have been sent to the language server
12303    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
12304    cx.lsp
12305        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
12306            let buffer_changes = buffer_changes.clone();
12307            move |params, _| {
12308                buffer_changes.lock().extend(
12309                    params
12310                        .content_changes
12311                        .into_iter()
12312                        .map(|e| (e.range.unwrap(), e.text)),
12313                );
12314            }
12315        });
12316
12317    // Handle formatting requests to the language server.
12318    cx.lsp
12319        .set_request_handler::<lsp::request::Formatting, _, _>({
12320            let buffer_changes = buffer_changes.clone();
12321            move |_, _| {
12322                // When formatting is requested, trailing whitespace has already been stripped,
12323                // and the trailing newline has already been added.
12324                assert_eq!(
12325                    &buffer_changes.lock()[1..],
12326                    &[
12327                        (
12328                            lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
12329                            "".into()
12330                        ),
12331                        (
12332                            lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
12333                            "".into()
12334                        ),
12335                        (
12336                            lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
12337                            "\n".into()
12338                        ),
12339                    ]
12340                );
12341
12342                // Insert blank lines between each line of the buffer.
12343                async move {
12344                    Ok(Some(vec![
12345                        lsp::TextEdit {
12346                            range: lsp::Range::new(
12347                                lsp::Position::new(1, 0),
12348                                lsp::Position::new(1, 0),
12349                            ),
12350                            new_text: "\n".into(),
12351                        },
12352                        lsp::TextEdit {
12353                            range: lsp::Range::new(
12354                                lsp::Position::new(2, 0),
12355                                lsp::Position::new(2, 0),
12356                            ),
12357                            new_text: "\n".into(),
12358                        },
12359                    ]))
12360                }
12361            }
12362        });
12363
12364    // After formatting the buffer, the trailing whitespace is stripped,
12365    // a newline is appended, and the edits provided by the language server
12366    // have been applied.
12367    format.await.unwrap();
12368    cx.assert_editor_state(
12369        &[
12370            "one",   //
12371            "",      //
12372            "twoˇ",  //
12373            "",      //
12374            "three", //
12375            "four",  //
12376            "",      //
12377        ]
12378        .join("\n"),
12379    );
12380
12381    // Undoing the formatting undoes the trailing whitespace removal, the
12382    // trailing newline, and the LSP edits.
12383    cx.update_buffer(|buffer, cx| buffer.undo(cx));
12384    cx.assert_editor_state(
12385        &[
12386            "one ",   //
12387            "twoˇ",   //
12388            "three ", //
12389            "four",   //
12390        ]
12391        .join("\n"),
12392    );
12393}
12394
12395#[gpui::test]
12396async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
12397    cx: &mut TestAppContext,
12398) {
12399    init_test(cx, |_| {});
12400
12401    cx.update(|cx| {
12402        cx.update_global::<SettingsStore, _>(|settings, cx| {
12403            settings.update_user_settings(cx, |settings| {
12404                settings.editor.auto_signature_help = Some(true);
12405            });
12406        });
12407    });
12408
12409    let mut cx = EditorLspTestContext::new_rust(
12410        lsp::ServerCapabilities {
12411            signature_help_provider: Some(lsp::SignatureHelpOptions {
12412                ..Default::default()
12413            }),
12414            ..Default::default()
12415        },
12416        cx,
12417    )
12418    .await;
12419
12420    let language = Language::new(
12421        LanguageConfig {
12422            name: "Rust".into(),
12423            brackets: BracketPairConfig {
12424                pairs: vec![
12425                    BracketPair {
12426                        start: "{".to_string(),
12427                        end: "}".to_string(),
12428                        close: true,
12429                        surround: true,
12430                        newline: true,
12431                    },
12432                    BracketPair {
12433                        start: "(".to_string(),
12434                        end: ")".to_string(),
12435                        close: true,
12436                        surround: true,
12437                        newline: true,
12438                    },
12439                    BracketPair {
12440                        start: "/*".to_string(),
12441                        end: " */".to_string(),
12442                        close: true,
12443                        surround: true,
12444                        newline: true,
12445                    },
12446                    BracketPair {
12447                        start: "[".to_string(),
12448                        end: "]".to_string(),
12449                        close: false,
12450                        surround: false,
12451                        newline: true,
12452                    },
12453                    BracketPair {
12454                        start: "\"".to_string(),
12455                        end: "\"".to_string(),
12456                        close: true,
12457                        surround: true,
12458                        newline: false,
12459                    },
12460                    BracketPair {
12461                        start: "<".to_string(),
12462                        end: ">".to_string(),
12463                        close: false,
12464                        surround: true,
12465                        newline: true,
12466                    },
12467                ],
12468                ..Default::default()
12469            },
12470            autoclose_before: "})]".to_string(),
12471            ..Default::default()
12472        },
12473        Some(tree_sitter_rust::LANGUAGE.into()),
12474    );
12475    let language = Arc::new(language);
12476
12477    cx.language_registry().add(language.clone());
12478    cx.update_buffer(|buffer, cx| {
12479        buffer.set_language(Some(language), cx);
12480    });
12481
12482    cx.set_state(
12483        &r#"
12484            fn main() {
12485                sampleˇ
12486            }
12487        "#
12488        .unindent(),
12489    );
12490
12491    cx.update_editor(|editor, window, cx| {
12492        editor.handle_input("(", window, cx);
12493    });
12494    cx.assert_editor_state(
12495        &"
12496            fn main() {
12497                sample(ˇ)
12498            }
12499        "
12500        .unindent(),
12501    );
12502
12503    let mocked_response = lsp::SignatureHelp {
12504        signatures: vec![lsp::SignatureInformation {
12505            label: "fn sample(param1: u8, param2: u8)".to_string(),
12506            documentation: None,
12507            parameters: Some(vec![
12508                lsp::ParameterInformation {
12509                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12510                    documentation: None,
12511                },
12512                lsp::ParameterInformation {
12513                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12514                    documentation: None,
12515                },
12516            ]),
12517            active_parameter: None,
12518        }],
12519        active_signature: Some(0),
12520        active_parameter: Some(0),
12521    };
12522    handle_signature_help_request(&mut cx, mocked_response).await;
12523
12524    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12525        .await;
12526
12527    cx.editor(|editor, _, _| {
12528        let signature_help_state = editor.signature_help_state.popover().cloned();
12529        let signature = signature_help_state.unwrap();
12530        assert_eq!(
12531            signature.signatures[signature.current_signature].label,
12532            "fn sample(param1: u8, param2: u8)"
12533        );
12534    });
12535}
12536
12537#[gpui::test]
12538async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
12539    init_test(cx, |_| {});
12540
12541    cx.update(|cx| {
12542        cx.update_global::<SettingsStore, _>(|settings, cx| {
12543            settings.update_user_settings(cx, |settings| {
12544                settings.editor.auto_signature_help = Some(false);
12545                settings.editor.show_signature_help_after_edits = Some(false);
12546            });
12547        });
12548    });
12549
12550    let mut cx = EditorLspTestContext::new_rust(
12551        lsp::ServerCapabilities {
12552            signature_help_provider: Some(lsp::SignatureHelpOptions {
12553                ..Default::default()
12554            }),
12555            ..Default::default()
12556        },
12557        cx,
12558    )
12559    .await;
12560
12561    let language = Language::new(
12562        LanguageConfig {
12563            name: "Rust".into(),
12564            brackets: BracketPairConfig {
12565                pairs: vec![
12566                    BracketPair {
12567                        start: "{".to_string(),
12568                        end: "}".to_string(),
12569                        close: true,
12570                        surround: true,
12571                        newline: true,
12572                    },
12573                    BracketPair {
12574                        start: "(".to_string(),
12575                        end: ")".to_string(),
12576                        close: true,
12577                        surround: true,
12578                        newline: true,
12579                    },
12580                    BracketPair {
12581                        start: "/*".to_string(),
12582                        end: " */".to_string(),
12583                        close: true,
12584                        surround: true,
12585                        newline: true,
12586                    },
12587                    BracketPair {
12588                        start: "[".to_string(),
12589                        end: "]".to_string(),
12590                        close: false,
12591                        surround: false,
12592                        newline: true,
12593                    },
12594                    BracketPair {
12595                        start: "\"".to_string(),
12596                        end: "\"".to_string(),
12597                        close: true,
12598                        surround: true,
12599                        newline: false,
12600                    },
12601                    BracketPair {
12602                        start: "<".to_string(),
12603                        end: ">".to_string(),
12604                        close: false,
12605                        surround: true,
12606                        newline: true,
12607                    },
12608                ],
12609                ..Default::default()
12610            },
12611            autoclose_before: "})]".to_string(),
12612            ..Default::default()
12613        },
12614        Some(tree_sitter_rust::LANGUAGE.into()),
12615    );
12616    let language = Arc::new(language);
12617
12618    cx.language_registry().add(language.clone());
12619    cx.update_buffer(|buffer, cx| {
12620        buffer.set_language(Some(language), cx);
12621    });
12622
12623    // Ensure that signature_help is not called when no signature help is enabled.
12624    cx.set_state(
12625        &r#"
12626            fn main() {
12627                sampleˇ
12628            }
12629        "#
12630        .unindent(),
12631    );
12632    cx.update_editor(|editor, window, cx| {
12633        editor.handle_input("(", window, cx);
12634    });
12635    cx.assert_editor_state(
12636        &"
12637            fn main() {
12638                sample(ˇ)
12639            }
12640        "
12641        .unindent(),
12642    );
12643    cx.editor(|editor, _, _| {
12644        assert!(editor.signature_help_state.task().is_none());
12645    });
12646
12647    let mocked_response = lsp::SignatureHelp {
12648        signatures: vec![lsp::SignatureInformation {
12649            label: "fn sample(param1: u8, param2: u8)".to_string(),
12650            documentation: None,
12651            parameters: Some(vec![
12652                lsp::ParameterInformation {
12653                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12654                    documentation: None,
12655                },
12656                lsp::ParameterInformation {
12657                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12658                    documentation: None,
12659                },
12660            ]),
12661            active_parameter: None,
12662        }],
12663        active_signature: Some(0),
12664        active_parameter: Some(0),
12665    };
12666
12667    // Ensure that signature_help is called when enabled afte edits
12668    cx.update(|_, cx| {
12669        cx.update_global::<SettingsStore, _>(|settings, cx| {
12670            settings.update_user_settings(cx, |settings| {
12671                settings.editor.auto_signature_help = Some(false);
12672                settings.editor.show_signature_help_after_edits = Some(true);
12673            });
12674        });
12675    });
12676    cx.set_state(
12677        &r#"
12678            fn main() {
12679                sampleˇ
12680            }
12681        "#
12682        .unindent(),
12683    );
12684    cx.update_editor(|editor, window, cx| {
12685        editor.handle_input("(", window, cx);
12686    });
12687    cx.assert_editor_state(
12688        &"
12689            fn main() {
12690                sample(ˇ)
12691            }
12692        "
12693        .unindent(),
12694    );
12695    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12696    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12697        .await;
12698    cx.update_editor(|editor, _, _| {
12699        let signature_help_state = editor.signature_help_state.popover().cloned();
12700        assert!(signature_help_state.is_some());
12701        let signature = signature_help_state.unwrap();
12702        assert_eq!(
12703            signature.signatures[signature.current_signature].label,
12704            "fn sample(param1: u8, param2: u8)"
12705        );
12706        editor.signature_help_state = SignatureHelpState::default();
12707    });
12708
12709    // Ensure that signature_help is called when auto signature help override is enabled
12710    cx.update(|_, cx| {
12711        cx.update_global::<SettingsStore, _>(|settings, cx| {
12712            settings.update_user_settings(cx, |settings| {
12713                settings.editor.auto_signature_help = Some(true);
12714                settings.editor.show_signature_help_after_edits = Some(false);
12715            });
12716        });
12717    });
12718    cx.set_state(
12719        &r#"
12720            fn main() {
12721                sampleˇ
12722            }
12723        "#
12724        .unindent(),
12725    );
12726    cx.update_editor(|editor, window, cx| {
12727        editor.handle_input("(", window, cx);
12728    });
12729    cx.assert_editor_state(
12730        &"
12731            fn main() {
12732                sample(ˇ)
12733            }
12734        "
12735        .unindent(),
12736    );
12737    handle_signature_help_request(&mut cx, mocked_response).await;
12738    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12739        .await;
12740    cx.editor(|editor, _, _| {
12741        let signature_help_state = editor.signature_help_state.popover().cloned();
12742        assert!(signature_help_state.is_some());
12743        let signature = signature_help_state.unwrap();
12744        assert_eq!(
12745            signature.signatures[signature.current_signature].label,
12746            "fn sample(param1: u8, param2: u8)"
12747        );
12748    });
12749}
12750
12751#[gpui::test]
12752async fn test_signature_help(cx: &mut TestAppContext) {
12753    init_test(cx, |_| {});
12754    cx.update(|cx| {
12755        cx.update_global::<SettingsStore, _>(|settings, cx| {
12756            settings.update_user_settings(cx, |settings| {
12757                settings.editor.auto_signature_help = Some(true);
12758            });
12759        });
12760    });
12761
12762    let mut cx = EditorLspTestContext::new_rust(
12763        lsp::ServerCapabilities {
12764            signature_help_provider: Some(lsp::SignatureHelpOptions {
12765                ..Default::default()
12766            }),
12767            ..Default::default()
12768        },
12769        cx,
12770    )
12771    .await;
12772
12773    // A test that directly calls `show_signature_help`
12774    cx.update_editor(|editor, window, cx| {
12775        editor.show_signature_help(&ShowSignatureHelp, window, cx);
12776    });
12777
12778    let mocked_response = lsp::SignatureHelp {
12779        signatures: vec![lsp::SignatureInformation {
12780            label: "fn sample(param1: u8, param2: u8)".to_string(),
12781            documentation: None,
12782            parameters: Some(vec![
12783                lsp::ParameterInformation {
12784                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12785                    documentation: None,
12786                },
12787                lsp::ParameterInformation {
12788                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12789                    documentation: None,
12790                },
12791            ]),
12792            active_parameter: None,
12793        }],
12794        active_signature: Some(0),
12795        active_parameter: Some(0),
12796    };
12797    handle_signature_help_request(&mut cx, mocked_response).await;
12798
12799    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12800        .await;
12801
12802    cx.editor(|editor, _, _| {
12803        let signature_help_state = editor.signature_help_state.popover().cloned();
12804        assert!(signature_help_state.is_some());
12805        let signature = signature_help_state.unwrap();
12806        assert_eq!(
12807            signature.signatures[signature.current_signature].label,
12808            "fn sample(param1: u8, param2: u8)"
12809        );
12810    });
12811
12812    // When exiting outside from inside the brackets, `signature_help` is closed.
12813    cx.set_state(indoc! {"
12814        fn main() {
12815            sample(ˇ);
12816        }
12817
12818        fn sample(param1: u8, param2: u8) {}
12819    "});
12820
12821    cx.update_editor(|editor, window, cx| {
12822        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12823            s.select_ranges([0..0])
12824        });
12825    });
12826
12827    let mocked_response = lsp::SignatureHelp {
12828        signatures: Vec::new(),
12829        active_signature: None,
12830        active_parameter: None,
12831    };
12832    handle_signature_help_request(&mut cx, mocked_response).await;
12833
12834    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
12835        .await;
12836
12837    cx.editor(|editor, _, _| {
12838        assert!(!editor.signature_help_state.is_shown());
12839    });
12840
12841    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
12842    cx.set_state(indoc! {"
12843        fn main() {
12844            sample(ˇ);
12845        }
12846
12847        fn sample(param1: u8, param2: u8) {}
12848    "});
12849
12850    let mocked_response = lsp::SignatureHelp {
12851        signatures: vec![lsp::SignatureInformation {
12852            label: "fn sample(param1: u8, param2: u8)".to_string(),
12853            documentation: None,
12854            parameters: Some(vec![
12855                lsp::ParameterInformation {
12856                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12857                    documentation: None,
12858                },
12859                lsp::ParameterInformation {
12860                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12861                    documentation: None,
12862                },
12863            ]),
12864            active_parameter: None,
12865        }],
12866        active_signature: Some(0),
12867        active_parameter: Some(0),
12868    };
12869    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12870    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12871        .await;
12872    cx.editor(|editor, _, _| {
12873        assert!(editor.signature_help_state.is_shown());
12874    });
12875
12876    // Restore the popover with more parameter input
12877    cx.set_state(indoc! {"
12878        fn main() {
12879            sample(param1, param2ˇ);
12880        }
12881
12882        fn sample(param1: u8, param2: u8) {}
12883    "});
12884
12885    let mocked_response = lsp::SignatureHelp {
12886        signatures: vec![lsp::SignatureInformation {
12887            label: "fn sample(param1: u8, param2: u8)".to_string(),
12888            documentation: None,
12889            parameters: Some(vec![
12890                lsp::ParameterInformation {
12891                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12892                    documentation: None,
12893                },
12894                lsp::ParameterInformation {
12895                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12896                    documentation: None,
12897                },
12898            ]),
12899            active_parameter: None,
12900        }],
12901        active_signature: Some(0),
12902        active_parameter: Some(1),
12903    };
12904    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12905    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12906        .await;
12907
12908    // When selecting a range, the popover is gone.
12909    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
12910    cx.update_editor(|editor, window, cx| {
12911        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12912            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
12913        })
12914    });
12915    cx.assert_editor_state(indoc! {"
12916        fn main() {
12917            sample(param1, «ˇparam2»);
12918        }
12919
12920        fn sample(param1: u8, param2: u8) {}
12921    "});
12922    cx.editor(|editor, _, _| {
12923        assert!(!editor.signature_help_state.is_shown());
12924    });
12925
12926    // When unselecting again, the popover is back if within the brackets.
12927    cx.update_editor(|editor, window, cx| {
12928        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12929            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
12930        })
12931    });
12932    cx.assert_editor_state(indoc! {"
12933        fn main() {
12934            sample(param1, ˇparam2);
12935        }
12936
12937        fn sample(param1: u8, param2: u8) {}
12938    "});
12939    handle_signature_help_request(&mut cx, mocked_response).await;
12940    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12941        .await;
12942    cx.editor(|editor, _, _| {
12943        assert!(editor.signature_help_state.is_shown());
12944    });
12945
12946    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
12947    cx.update_editor(|editor, window, cx| {
12948        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12949            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
12950            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
12951        })
12952    });
12953    cx.assert_editor_state(indoc! {"
12954        fn main() {
12955            sample(param1, ˇparam2);
12956        }
12957
12958        fn sample(param1: u8, param2: u8) {}
12959    "});
12960
12961    let mocked_response = lsp::SignatureHelp {
12962        signatures: vec![lsp::SignatureInformation {
12963            label: "fn sample(param1: u8, param2: u8)".to_string(),
12964            documentation: None,
12965            parameters: Some(vec![
12966                lsp::ParameterInformation {
12967                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12968                    documentation: None,
12969                },
12970                lsp::ParameterInformation {
12971                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12972                    documentation: None,
12973                },
12974            ]),
12975            active_parameter: None,
12976        }],
12977        active_signature: Some(0),
12978        active_parameter: Some(1),
12979    };
12980    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12981    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12982        .await;
12983    cx.update_editor(|editor, _, cx| {
12984        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
12985    });
12986    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
12987        .await;
12988    cx.update_editor(|editor, window, cx| {
12989        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12990            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
12991        })
12992    });
12993    cx.assert_editor_state(indoc! {"
12994        fn main() {
12995            sample(param1, «ˇparam2»);
12996        }
12997
12998        fn sample(param1: u8, param2: u8) {}
12999    "});
13000    cx.update_editor(|editor, window, cx| {
13001        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13002            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13003        })
13004    });
13005    cx.assert_editor_state(indoc! {"
13006        fn main() {
13007            sample(param1, ˇparam2);
13008        }
13009
13010        fn sample(param1: u8, param2: u8) {}
13011    "});
13012    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
13013        .await;
13014}
13015
13016#[gpui::test]
13017async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
13018    init_test(cx, |_| {});
13019
13020    let mut cx = EditorLspTestContext::new_rust(
13021        lsp::ServerCapabilities {
13022            signature_help_provider: Some(lsp::SignatureHelpOptions {
13023                ..Default::default()
13024            }),
13025            ..Default::default()
13026        },
13027        cx,
13028    )
13029    .await;
13030
13031    cx.set_state(indoc! {"
13032        fn main() {
13033            overloadedˇ
13034        }
13035    "});
13036
13037    cx.update_editor(|editor, window, cx| {
13038        editor.handle_input("(", window, cx);
13039        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13040    });
13041
13042    // Mock response with 3 signatures
13043    let mocked_response = lsp::SignatureHelp {
13044        signatures: vec![
13045            lsp::SignatureInformation {
13046                label: "fn overloaded(x: i32)".to_string(),
13047                documentation: None,
13048                parameters: Some(vec![lsp::ParameterInformation {
13049                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13050                    documentation: None,
13051                }]),
13052                active_parameter: None,
13053            },
13054            lsp::SignatureInformation {
13055                label: "fn overloaded(x: i32, y: i32)".to_string(),
13056                documentation: None,
13057                parameters: Some(vec![
13058                    lsp::ParameterInformation {
13059                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13060                        documentation: None,
13061                    },
13062                    lsp::ParameterInformation {
13063                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13064                        documentation: None,
13065                    },
13066                ]),
13067                active_parameter: None,
13068            },
13069            lsp::SignatureInformation {
13070                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
13071                documentation: None,
13072                parameters: Some(vec![
13073                    lsp::ParameterInformation {
13074                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13075                        documentation: None,
13076                    },
13077                    lsp::ParameterInformation {
13078                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13079                        documentation: None,
13080                    },
13081                    lsp::ParameterInformation {
13082                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
13083                        documentation: None,
13084                    },
13085                ]),
13086                active_parameter: None,
13087            },
13088        ],
13089        active_signature: Some(1),
13090        active_parameter: Some(0),
13091    };
13092    handle_signature_help_request(&mut cx, mocked_response).await;
13093
13094    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13095        .await;
13096
13097    // Verify we have multiple signatures and the right one is selected
13098    cx.editor(|editor, _, _| {
13099        let popover = editor.signature_help_state.popover().cloned().unwrap();
13100        assert_eq!(popover.signatures.len(), 3);
13101        // active_signature was 1, so that should be the current
13102        assert_eq!(popover.current_signature, 1);
13103        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
13104        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
13105        assert_eq!(
13106            popover.signatures[2].label,
13107            "fn overloaded(x: i32, y: i32, z: i32)"
13108        );
13109    });
13110
13111    // Test navigation functionality
13112    cx.update_editor(|editor, window, cx| {
13113        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13114    });
13115
13116    cx.editor(|editor, _, _| {
13117        let popover = editor.signature_help_state.popover().cloned().unwrap();
13118        assert_eq!(popover.current_signature, 2);
13119    });
13120
13121    // Test wrap around
13122    cx.update_editor(|editor, window, cx| {
13123        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13124    });
13125
13126    cx.editor(|editor, _, _| {
13127        let popover = editor.signature_help_state.popover().cloned().unwrap();
13128        assert_eq!(popover.current_signature, 0);
13129    });
13130
13131    // Test previous navigation
13132    cx.update_editor(|editor, window, cx| {
13133        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
13134    });
13135
13136    cx.editor(|editor, _, _| {
13137        let popover = editor.signature_help_state.popover().cloned().unwrap();
13138        assert_eq!(popover.current_signature, 2);
13139    });
13140}
13141
13142#[gpui::test]
13143async fn test_completion_mode(cx: &mut TestAppContext) {
13144    init_test(cx, |_| {});
13145    let mut cx = EditorLspTestContext::new_rust(
13146        lsp::ServerCapabilities {
13147            completion_provider: Some(lsp::CompletionOptions {
13148                resolve_provider: Some(true),
13149                ..Default::default()
13150            }),
13151            ..Default::default()
13152        },
13153        cx,
13154    )
13155    .await;
13156
13157    struct Run {
13158        run_description: &'static str,
13159        initial_state: String,
13160        buffer_marked_text: String,
13161        completion_label: &'static str,
13162        completion_text: &'static str,
13163        expected_with_insert_mode: String,
13164        expected_with_replace_mode: String,
13165        expected_with_replace_subsequence_mode: String,
13166        expected_with_replace_suffix_mode: String,
13167    }
13168
13169    let runs = [
13170        Run {
13171            run_description: "Start of word matches completion text",
13172            initial_state: "before ediˇ after".into(),
13173            buffer_marked_text: "before <edi|> after".into(),
13174            completion_label: "editor",
13175            completion_text: "editor",
13176            expected_with_insert_mode: "before editorˇ after".into(),
13177            expected_with_replace_mode: "before editorˇ after".into(),
13178            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13179            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13180        },
13181        Run {
13182            run_description: "Accept same text at the middle of the word",
13183            initial_state: "before ediˇtor after".into(),
13184            buffer_marked_text: "before <edi|tor> after".into(),
13185            completion_label: "editor",
13186            completion_text: "editor",
13187            expected_with_insert_mode: "before editorˇtor after".into(),
13188            expected_with_replace_mode: "before editorˇ after".into(),
13189            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13190            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13191        },
13192        Run {
13193            run_description: "End of word matches completion text -- cursor at end",
13194            initial_state: "before torˇ after".into(),
13195            buffer_marked_text: "before <tor|> after".into(),
13196            completion_label: "editor",
13197            completion_text: "editor",
13198            expected_with_insert_mode: "before editorˇ after".into(),
13199            expected_with_replace_mode: "before editorˇ after".into(),
13200            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13201            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13202        },
13203        Run {
13204            run_description: "End of word matches completion text -- cursor at start",
13205            initial_state: "before ˇtor after".into(),
13206            buffer_marked_text: "before <|tor> after".into(),
13207            completion_label: "editor",
13208            completion_text: "editor",
13209            expected_with_insert_mode: "before editorˇtor after".into(),
13210            expected_with_replace_mode: "before editorˇ after".into(),
13211            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13212            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13213        },
13214        Run {
13215            run_description: "Prepend text containing whitespace",
13216            initial_state: "pˇfield: bool".into(),
13217            buffer_marked_text: "<p|field>: bool".into(),
13218            completion_label: "pub ",
13219            completion_text: "pub ",
13220            expected_with_insert_mode: "pub ˇfield: bool".into(),
13221            expected_with_replace_mode: "pub ˇ: bool".into(),
13222            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
13223            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
13224        },
13225        Run {
13226            run_description: "Add element to start of list",
13227            initial_state: "[element_ˇelement_2]".into(),
13228            buffer_marked_text: "[<element_|element_2>]".into(),
13229            completion_label: "element_1",
13230            completion_text: "element_1",
13231            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
13232            expected_with_replace_mode: "[element_1ˇ]".into(),
13233            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
13234            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
13235        },
13236        Run {
13237            run_description: "Add element to start of list -- first and second elements are equal",
13238            initial_state: "[elˇelement]".into(),
13239            buffer_marked_text: "[<el|element>]".into(),
13240            completion_label: "element",
13241            completion_text: "element",
13242            expected_with_insert_mode: "[elementˇelement]".into(),
13243            expected_with_replace_mode: "[elementˇ]".into(),
13244            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
13245            expected_with_replace_suffix_mode: "[elementˇ]".into(),
13246        },
13247        Run {
13248            run_description: "Ends with matching suffix",
13249            initial_state: "SubˇError".into(),
13250            buffer_marked_text: "<Sub|Error>".into(),
13251            completion_label: "SubscriptionError",
13252            completion_text: "SubscriptionError",
13253            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
13254            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13255            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13256            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
13257        },
13258        Run {
13259            run_description: "Suffix is a subsequence -- contiguous",
13260            initial_state: "SubˇErr".into(),
13261            buffer_marked_text: "<Sub|Err>".into(),
13262            completion_label: "SubscriptionError",
13263            completion_text: "SubscriptionError",
13264            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
13265            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13266            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13267            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
13268        },
13269        Run {
13270            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
13271            initial_state: "Suˇscrirr".into(),
13272            buffer_marked_text: "<Su|scrirr>".into(),
13273            completion_label: "SubscriptionError",
13274            completion_text: "SubscriptionError",
13275            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
13276            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13277            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13278            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
13279        },
13280        Run {
13281            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
13282            initial_state: "foo(indˇix)".into(),
13283            buffer_marked_text: "foo(<ind|ix>)".into(),
13284            completion_label: "node_index",
13285            completion_text: "node_index",
13286            expected_with_insert_mode: "foo(node_indexˇix)".into(),
13287            expected_with_replace_mode: "foo(node_indexˇ)".into(),
13288            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
13289            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
13290        },
13291        Run {
13292            run_description: "Replace range ends before cursor - should extend to cursor",
13293            initial_state: "before editˇo after".into(),
13294            buffer_marked_text: "before <{ed}>it|o after".into(),
13295            completion_label: "editor",
13296            completion_text: "editor",
13297            expected_with_insert_mode: "before editorˇo after".into(),
13298            expected_with_replace_mode: "before editorˇo after".into(),
13299            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
13300            expected_with_replace_suffix_mode: "before editorˇo after".into(),
13301        },
13302        Run {
13303            run_description: "Uses label for suffix matching",
13304            initial_state: "before ediˇtor after".into(),
13305            buffer_marked_text: "before <edi|tor> after".into(),
13306            completion_label: "editor",
13307            completion_text: "editor()",
13308            expected_with_insert_mode: "before editor()ˇtor after".into(),
13309            expected_with_replace_mode: "before editor()ˇ after".into(),
13310            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
13311            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
13312        },
13313        Run {
13314            run_description: "Case insensitive subsequence and suffix matching",
13315            initial_state: "before EDiˇtoR after".into(),
13316            buffer_marked_text: "before <EDi|toR> after".into(),
13317            completion_label: "editor",
13318            completion_text: "editor",
13319            expected_with_insert_mode: "before editorˇtoR after".into(),
13320            expected_with_replace_mode: "before editorˇ after".into(),
13321            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13322            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13323        },
13324    ];
13325
13326    for run in runs {
13327        let run_variations = [
13328            (LspInsertMode::Insert, run.expected_with_insert_mode),
13329            (LspInsertMode::Replace, run.expected_with_replace_mode),
13330            (
13331                LspInsertMode::ReplaceSubsequence,
13332                run.expected_with_replace_subsequence_mode,
13333            ),
13334            (
13335                LspInsertMode::ReplaceSuffix,
13336                run.expected_with_replace_suffix_mode,
13337            ),
13338        ];
13339
13340        for (lsp_insert_mode, expected_text) in run_variations {
13341            eprintln!(
13342                "run = {:?}, mode = {lsp_insert_mode:.?}",
13343                run.run_description,
13344            );
13345
13346            update_test_language_settings(&mut cx, |settings| {
13347                settings.defaults.completions = Some(CompletionSettingsContent {
13348                    lsp_insert_mode: Some(lsp_insert_mode),
13349                    words: Some(WordsCompletionMode::Disabled),
13350                    words_min_length: Some(0),
13351                    ..Default::default()
13352                });
13353            });
13354
13355            cx.set_state(&run.initial_state);
13356            cx.update_editor(|editor, window, cx| {
13357                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13358            });
13359
13360            let counter = Arc::new(AtomicUsize::new(0));
13361            handle_completion_request_with_insert_and_replace(
13362                &mut cx,
13363                &run.buffer_marked_text,
13364                vec![(run.completion_label, run.completion_text)],
13365                counter.clone(),
13366            )
13367            .await;
13368            cx.condition(|editor, _| editor.context_menu_visible())
13369                .await;
13370            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13371
13372            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13373                editor
13374                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
13375                    .unwrap()
13376            });
13377            cx.assert_editor_state(&expected_text);
13378            handle_resolve_completion_request(&mut cx, None).await;
13379            apply_additional_edits.await.unwrap();
13380        }
13381    }
13382}
13383
13384#[gpui::test]
13385async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
13386    init_test(cx, |_| {});
13387    let mut cx = EditorLspTestContext::new_rust(
13388        lsp::ServerCapabilities {
13389            completion_provider: Some(lsp::CompletionOptions {
13390                resolve_provider: Some(true),
13391                ..Default::default()
13392            }),
13393            ..Default::default()
13394        },
13395        cx,
13396    )
13397    .await;
13398
13399    let initial_state = "SubˇError";
13400    let buffer_marked_text = "<Sub|Error>";
13401    let completion_text = "SubscriptionError";
13402    let expected_with_insert_mode = "SubscriptionErrorˇError";
13403    let expected_with_replace_mode = "SubscriptionErrorˇ";
13404
13405    update_test_language_settings(&mut cx, |settings| {
13406        settings.defaults.completions = Some(CompletionSettingsContent {
13407            words: Some(WordsCompletionMode::Disabled),
13408            words_min_length: Some(0),
13409            // set the opposite here to ensure that the action is overriding the default behavior
13410            lsp_insert_mode: Some(LspInsertMode::Insert),
13411            ..Default::default()
13412        });
13413    });
13414
13415    cx.set_state(initial_state);
13416    cx.update_editor(|editor, window, cx| {
13417        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13418    });
13419
13420    let counter = Arc::new(AtomicUsize::new(0));
13421    handle_completion_request_with_insert_and_replace(
13422        &mut cx,
13423        buffer_marked_text,
13424        vec![(completion_text, completion_text)],
13425        counter.clone(),
13426    )
13427    .await;
13428    cx.condition(|editor, _| editor.context_menu_visible())
13429        .await;
13430    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13431
13432    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13433        editor
13434            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13435            .unwrap()
13436    });
13437    cx.assert_editor_state(expected_with_replace_mode);
13438    handle_resolve_completion_request(&mut cx, None).await;
13439    apply_additional_edits.await.unwrap();
13440
13441    update_test_language_settings(&mut cx, |settings| {
13442        settings.defaults.completions = Some(CompletionSettingsContent {
13443            words: Some(WordsCompletionMode::Disabled),
13444            words_min_length: Some(0),
13445            // set the opposite here to ensure that the action is overriding the default behavior
13446            lsp_insert_mode: Some(LspInsertMode::Replace),
13447            ..Default::default()
13448        });
13449    });
13450
13451    cx.set_state(initial_state);
13452    cx.update_editor(|editor, window, cx| {
13453        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13454    });
13455    handle_completion_request_with_insert_and_replace(
13456        &mut cx,
13457        buffer_marked_text,
13458        vec![(completion_text, completion_text)],
13459        counter.clone(),
13460    )
13461    .await;
13462    cx.condition(|editor, _| editor.context_menu_visible())
13463        .await;
13464    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
13465
13466    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13467        editor
13468            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
13469            .unwrap()
13470    });
13471    cx.assert_editor_state(expected_with_insert_mode);
13472    handle_resolve_completion_request(&mut cx, None).await;
13473    apply_additional_edits.await.unwrap();
13474}
13475
13476#[gpui::test]
13477async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
13478    init_test(cx, |_| {});
13479    let mut cx = EditorLspTestContext::new_rust(
13480        lsp::ServerCapabilities {
13481            completion_provider: Some(lsp::CompletionOptions {
13482                resolve_provider: Some(true),
13483                ..Default::default()
13484            }),
13485            ..Default::default()
13486        },
13487        cx,
13488    )
13489    .await;
13490
13491    // scenario: surrounding text matches completion text
13492    let completion_text = "to_offset";
13493    let initial_state = indoc! {"
13494        1. buf.to_offˇsuffix
13495        2. buf.to_offˇsuf
13496        3. buf.to_offˇfix
13497        4. buf.to_offˇ
13498        5. into_offˇensive
13499        6. ˇsuffix
13500        7. let ˇ //
13501        8. aaˇzz
13502        9. buf.to_off«zzzzzˇ»suffix
13503        10. buf.«ˇzzzzz»suffix
13504        11. to_off«ˇzzzzz»
13505
13506        buf.to_offˇsuffix  // newest cursor
13507    "};
13508    let completion_marked_buffer = indoc! {"
13509        1. buf.to_offsuffix
13510        2. buf.to_offsuf
13511        3. buf.to_offfix
13512        4. buf.to_off
13513        5. into_offensive
13514        6. suffix
13515        7. let  //
13516        8. aazz
13517        9. buf.to_offzzzzzsuffix
13518        10. buf.zzzzzsuffix
13519        11. to_offzzzzz
13520
13521        buf.<to_off|suffix>  // newest cursor
13522    "};
13523    let expected = indoc! {"
13524        1. buf.to_offsetˇ
13525        2. buf.to_offsetˇsuf
13526        3. buf.to_offsetˇfix
13527        4. buf.to_offsetˇ
13528        5. into_offsetˇensive
13529        6. to_offsetˇsuffix
13530        7. let to_offsetˇ //
13531        8. aato_offsetˇzz
13532        9. buf.to_offsetˇ
13533        10. buf.to_offsetˇsuffix
13534        11. to_offsetˇ
13535
13536        buf.to_offsetˇ  // newest cursor
13537    "};
13538    cx.set_state(initial_state);
13539    cx.update_editor(|editor, window, cx| {
13540        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13541    });
13542    handle_completion_request_with_insert_and_replace(
13543        &mut cx,
13544        completion_marked_buffer,
13545        vec![(completion_text, completion_text)],
13546        Arc::new(AtomicUsize::new(0)),
13547    )
13548    .await;
13549    cx.condition(|editor, _| editor.context_menu_visible())
13550        .await;
13551    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13552        editor
13553            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13554            .unwrap()
13555    });
13556    cx.assert_editor_state(expected);
13557    handle_resolve_completion_request(&mut cx, None).await;
13558    apply_additional_edits.await.unwrap();
13559
13560    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
13561    let completion_text = "foo_and_bar";
13562    let initial_state = indoc! {"
13563        1. ooanbˇ
13564        2. zooanbˇ
13565        3. ooanbˇz
13566        4. zooanbˇz
13567        5. ooanˇ
13568        6. oanbˇ
13569
13570        ooanbˇ
13571    "};
13572    let completion_marked_buffer = indoc! {"
13573        1. ooanb
13574        2. zooanb
13575        3. ooanbz
13576        4. zooanbz
13577        5. ooan
13578        6. oanb
13579
13580        <ooanb|>
13581    "};
13582    let expected = indoc! {"
13583        1. foo_and_barˇ
13584        2. zfoo_and_barˇ
13585        3. foo_and_barˇz
13586        4. zfoo_and_barˇz
13587        5. ooanfoo_and_barˇ
13588        6. oanbfoo_and_barˇ
13589
13590        foo_and_barˇ
13591    "};
13592    cx.set_state(initial_state);
13593    cx.update_editor(|editor, window, cx| {
13594        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13595    });
13596    handle_completion_request_with_insert_and_replace(
13597        &mut cx,
13598        completion_marked_buffer,
13599        vec![(completion_text, completion_text)],
13600        Arc::new(AtomicUsize::new(0)),
13601    )
13602    .await;
13603    cx.condition(|editor, _| editor.context_menu_visible())
13604        .await;
13605    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13606        editor
13607            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13608            .unwrap()
13609    });
13610    cx.assert_editor_state(expected);
13611    handle_resolve_completion_request(&mut cx, None).await;
13612    apply_additional_edits.await.unwrap();
13613
13614    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
13615    // (expects the same as if it was inserted at the end)
13616    let completion_text = "foo_and_bar";
13617    let initial_state = indoc! {"
13618        1. ooˇanb
13619        2. zooˇanb
13620        3. ooˇanbz
13621        4. zooˇanbz
13622
13623        ooˇanb
13624    "};
13625    let completion_marked_buffer = indoc! {"
13626        1. ooanb
13627        2. zooanb
13628        3. ooanbz
13629        4. zooanbz
13630
13631        <oo|anb>
13632    "};
13633    let expected = indoc! {"
13634        1. foo_and_barˇ
13635        2. zfoo_and_barˇ
13636        3. foo_and_barˇz
13637        4. zfoo_and_barˇz
13638
13639        foo_and_barˇ
13640    "};
13641    cx.set_state(initial_state);
13642    cx.update_editor(|editor, window, cx| {
13643        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13644    });
13645    handle_completion_request_with_insert_and_replace(
13646        &mut cx,
13647        completion_marked_buffer,
13648        vec![(completion_text, completion_text)],
13649        Arc::new(AtomicUsize::new(0)),
13650    )
13651    .await;
13652    cx.condition(|editor, _| editor.context_menu_visible())
13653        .await;
13654    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13655        editor
13656            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13657            .unwrap()
13658    });
13659    cx.assert_editor_state(expected);
13660    handle_resolve_completion_request(&mut cx, None).await;
13661    apply_additional_edits.await.unwrap();
13662}
13663
13664// This used to crash
13665#[gpui::test]
13666async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
13667    init_test(cx, |_| {});
13668
13669    let buffer_text = indoc! {"
13670        fn main() {
13671            10.satu;
13672
13673            //
13674            // separate cursors so they open in different excerpts (manually reproducible)
13675            //
13676
13677            10.satu20;
13678        }
13679    "};
13680    let multibuffer_text_with_selections = indoc! {"
13681        fn main() {
13682            10.satuˇ;
13683
13684            //
13685
13686            //
13687
13688            10.satuˇ20;
13689        }
13690    "};
13691    let expected_multibuffer = indoc! {"
13692        fn main() {
13693            10.saturating_sub()ˇ;
13694
13695            //
13696
13697            //
13698
13699            10.saturating_sub()ˇ;
13700        }
13701    "};
13702
13703    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
13704    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
13705
13706    let fs = FakeFs::new(cx.executor());
13707    fs.insert_tree(
13708        path!("/a"),
13709        json!({
13710            "main.rs": buffer_text,
13711        }),
13712    )
13713    .await;
13714
13715    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13716    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13717    language_registry.add(rust_lang());
13718    let mut fake_servers = language_registry.register_fake_lsp(
13719        "Rust",
13720        FakeLspAdapter {
13721            capabilities: lsp::ServerCapabilities {
13722                completion_provider: Some(lsp::CompletionOptions {
13723                    resolve_provider: None,
13724                    ..lsp::CompletionOptions::default()
13725                }),
13726                ..lsp::ServerCapabilities::default()
13727            },
13728            ..FakeLspAdapter::default()
13729        },
13730    );
13731    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13732    let cx = &mut VisualTestContext::from_window(*workspace, cx);
13733    let buffer = project
13734        .update(cx, |project, cx| {
13735            project.open_local_buffer(path!("/a/main.rs"), cx)
13736        })
13737        .await
13738        .unwrap();
13739
13740    let multi_buffer = cx.new(|cx| {
13741        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
13742        multi_buffer.push_excerpts(
13743            buffer.clone(),
13744            [ExcerptRange::new(0..first_excerpt_end)],
13745            cx,
13746        );
13747        multi_buffer.push_excerpts(
13748            buffer.clone(),
13749            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
13750            cx,
13751        );
13752        multi_buffer
13753    });
13754
13755    let editor = workspace
13756        .update(cx, |_, window, cx| {
13757            cx.new(|cx| {
13758                Editor::new(
13759                    EditorMode::Full {
13760                        scale_ui_elements_with_buffer_font_size: false,
13761                        show_active_line_background: false,
13762                        sized_by_content: false,
13763                    },
13764                    multi_buffer.clone(),
13765                    Some(project.clone()),
13766                    window,
13767                    cx,
13768                )
13769            })
13770        })
13771        .unwrap();
13772
13773    let pane = workspace
13774        .update(cx, |workspace, _, _| workspace.active_pane().clone())
13775        .unwrap();
13776    pane.update_in(cx, |pane, window, cx| {
13777        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
13778    });
13779
13780    let fake_server = fake_servers.next().await.unwrap();
13781
13782    editor.update_in(cx, |editor, window, cx| {
13783        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13784            s.select_ranges([
13785                Point::new(1, 11)..Point::new(1, 11),
13786                Point::new(7, 11)..Point::new(7, 11),
13787            ])
13788        });
13789
13790        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
13791    });
13792
13793    editor.update_in(cx, |editor, window, cx| {
13794        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13795    });
13796
13797    fake_server
13798        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13799            let completion_item = lsp::CompletionItem {
13800                label: "saturating_sub()".into(),
13801                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
13802                    lsp::InsertReplaceEdit {
13803                        new_text: "saturating_sub()".to_owned(),
13804                        insert: lsp::Range::new(
13805                            lsp::Position::new(7, 7),
13806                            lsp::Position::new(7, 11),
13807                        ),
13808                        replace: lsp::Range::new(
13809                            lsp::Position::new(7, 7),
13810                            lsp::Position::new(7, 13),
13811                        ),
13812                    },
13813                )),
13814                ..lsp::CompletionItem::default()
13815            };
13816
13817            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
13818        })
13819        .next()
13820        .await
13821        .unwrap();
13822
13823    cx.condition(&editor, |editor, _| editor.context_menu_visible())
13824        .await;
13825
13826    editor
13827        .update_in(cx, |editor, window, cx| {
13828            editor
13829                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13830                .unwrap()
13831        })
13832        .await
13833        .unwrap();
13834
13835    editor.update(cx, |editor, cx| {
13836        assert_text_with_selections(editor, expected_multibuffer, cx);
13837    })
13838}
13839
13840#[gpui::test]
13841async fn test_completion(cx: &mut TestAppContext) {
13842    init_test(cx, |_| {});
13843
13844    let mut cx = EditorLspTestContext::new_rust(
13845        lsp::ServerCapabilities {
13846            completion_provider: Some(lsp::CompletionOptions {
13847                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
13848                resolve_provider: Some(true),
13849                ..Default::default()
13850            }),
13851            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13852            ..Default::default()
13853        },
13854        cx,
13855    )
13856    .await;
13857    let counter = Arc::new(AtomicUsize::new(0));
13858
13859    cx.set_state(indoc! {"
13860        oneˇ
13861        two
13862        three
13863    "});
13864    cx.simulate_keystroke(".");
13865    handle_completion_request(
13866        indoc! {"
13867            one.|<>
13868            two
13869            three
13870        "},
13871        vec!["first_completion", "second_completion"],
13872        true,
13873        counter.clone(),
13874        &mut cx,
13875    )
13876    .await;
13877    cx.condition(|editor, _| editor.context_menu_visible())
13878        .await;
13879    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13880
13881    let _handler = handle_signature_help_request(
13882        &mut cx,
13883        lsp::SignatureHelp {
13884            signatures: vec![lsp::SignatureInformation {
13885                label: "test signature".to_string(),
13886                documentation: None,
13887                parameters: Some(vec![lsp::ParameterInformation {
13888                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
13889                    documentation: None,
13890                }]),
13891                active_parameter: None,
13892            }],
13893            active_signature: None,
13894            active_parameter: None,
13895        },
13896    );
13897    cx.update_editor(|editor, window, cx| {
13898        assert!(
13899            !editor.signature_help_state.is_shown(),
13900            "No signature help was called for"
13901        );
13902        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13903    });
13904    cx.run_until_parked();
13905    cx.update_editor(|editor, _, _| {
13906        assert!(
13907            !editor.signature_help_state.is_shown(),
13908            "No signature help should be shown when completions menu is open"
13909        );
13910    });
13911
13912    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13913        editor.context_menu_next(&Default::default(), window, cx);
13914        editor
13915            .confirm_completion(&ConfirmCompletion::default(), window, cx)
13916            .unwrap()
13917    });
13918    cx.assert_editor_state(indoc! {"
13919        one.second_completionˇ
13920        two
13921        three
13922    "});
13923
13924    handle_resolve_completion_request(
13925        &mut cx,
13926        Some(vec![
13927            (
13928                //This overlaps with the primary completion edit which is
13929                //misbehavior from the LSP spec, test that we filter it out
13930                indoc! {"
13931                    one.second_ˇcompletion
13932                    two
13933                    threeˇ
13934                "},
13935                "overlapping additional edit",
13936            ),
13937            (
13938                indoc! {"
13939                    one.second_completion
13940                    two
13941                    threeˇ
13942                "},
13943                "\nadditional edit",
13944            ),
13945        ]),
13946    )
13947    .await;
13948    apply_additional_edits.await.unwrap();
13949    cx.assert_editor_state(indoc! {"
13950        one.second_completionˇ
13951        two
13952        three
13953        additional edit
13954    "});
13955
13956    cx.set_state(indoc! {"
13957        one.second_completion
13958        twoˇ
13959        threeˇ
13960        additional edit
13961    "});
13962    cx.simulate_keystroke(" ");
13963    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
13964    cx.simulate_keystroke("s");
13965    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
13966
13967    cx.assert_editor_state(indoc! {"
13968        one.second_completion
13969        two sˇ
13970        three sˇ
13971        additional edit
13972    "});
13973    handle_completion_request(
13974        indoc! {"
13975            one.second_completion
13976            two s
13977            three <s|>
13978            additional edit
13979        "},
13980        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
13981        true,
13982        counter.clone(),
13983        &mut cx,
13984    )
13985    .await;
13986    cx.condition(|editor, _| editor.context_menu_visible())
13987        .await;
13988    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
13989
13990    cx.simulate_keystroke("i");
13991
13992    handle_completion_request(
13993        indoc! {"
13994            one.second_completion
13995            two si
13996            three <si|>
13997            additional edit
13998        "},
13999        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14000        true,
14001        counter.clone(),
14002        &mut cx,
14003    )
14004    .await;
14005    cx.condition(|editor, _| editor.context_menu_visible())
14006        .await;
14007    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14008
14009    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14010        editor
14011            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14012            .unwrap()
14013    });
14014    cx.assert_editor_state(indoc! {"
14015        one.second_completion
14016        two sixth_completionˇ
14017        three sixth_completionˇ
14018        additional edit
14019    "});
14020
14021    apply_additional_edits.await.unwrap();
14022
14023    update_test_language_settings(&mut cx, |settings| {
14024        settings.defaults.show_completions_on_input = Some(false);
14025    });
14026    cx.set_state("editorˇ");
14027    cx.simulate_keystroke(".");
14028    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14029    cx.simulate_keystrokes("c l o");
14030    cx.assert_editor_state("editor.cloˇ");
14031    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14032    cx.update_editor(|editor, window, cx| {
14033        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14034    });
14035    handle_completion_request(
14036        "editor.<clo|>",
14037        vec!["close", "clobber"],
14038        true,
14039        counter.clone(),
14040        &mut cx,
14041    )
14042    .await;
14043    cx.condition(|editor, _| editor.context_menu_visible())
14044        .await;
14045    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
14046
14047    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14048        editor
14049            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14050            .unwrap()
14051    });
14052    cx.assert_editor_state("editor.clobberˇ");
14053    handle_resolve_completion_request(&mut cx, None).await;
14054    apply_additional_edits.await.unwrap();
14055}
14056
14057#[gpui::test]
14058async fn test_completion_reuse(cx: &mut TestAppContext) {
14059    init_test(cx, |_| {});
14060
14061    let mut cx = EditorLspTestContext::new_rust(
14062        lsp::ServerCapabilities {
14063            completion_provider: Some(lsp::CompletionOptions {
14064                trigger_characters: Some(vec![".".to_string()]),
14065                ..Default::default()
14066            }),
14067            ..Default::default()
14068        },
14069        cx,
14070    )
14071    .await;
14072
14073    let counter = Arc::new(AtomicUsize::new(0));
14074    cx.set_state("objˇ");
14075    cx.simulate_keystroke(".");
14076
14077    // Initial completion request returns complete results
14078    let is_incomplete = false;
14079    handle_completion_request(
14080        "obj.|<>",
14081        vec!["a", "ab", "abc"],
14082        is_incomplete,
14083        counter.clone(),
14084        &mut cx,
14085    )
14086    .await;
14087    cx.run_until_parked();
14088    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14089    cx.assert_editor_state("obj.ˇ");
14090    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14091
14092    // Type "a" - filters existing completions
14093    cx.simulate_keystroke("a");
14094    cx.run_until_parked();
14095    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14096    cx.assert_editor_state("obj.aˇ");
14097    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14098
14099    // Type "b" - filters existing completions
14100    cx.simulate_keystroke("b");
14101    cx.run_until_parked();
14102    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14103    cx.assert_editor_state("obj.abˇ");
14104    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14105
14106    // Type "c" - filters existing completions
14107    cx.simulate_keystroke("c");
14108    cx.run_until_parked();
14109    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14110    cx.assert_editor_state("obj.abcˇ");
14111    check_displayed_completions(vec!["abc"], &mut cx);
14112
14113    // Backspace to delete "c" - filters existing completions
14114    cx.update_editor(|editor, window, cx| {
14115        editor.backspace(&Backspace, window, cx);
14116    });
14117    cx.run_until_parked();
14118    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14119    cx.assert_editor_state("obj.abˇ");
14120    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14121
14122    // Moving cursor to the left dismisses menu.
14123    cx.update_editor(|editor, window, cx| {
14124        editor.move_left(&MoveLeft, window, cx);
14125    });
14126    cx.run_until_parked();
14127    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14128    cx.assert_editor_state("obj.aˇb");
14129    cx.update_editor(|editor, _, _| {
14130        assert_eq!(editor.context_menu_visible(), false);
14131    });
14132
14133    // Type "b" - new request
14134    cx.simulate_keystroke("b");
14135    let is_incomplete = false;
14136    handle_completion_request(
14137        "obj.<ab|>a",
14138        vec!["ab", "abc"],
14139        is_incomplete,
14140        counter.clone(),
14141        &mut cx,
14142    )
14143    .await;
14144    cx.run_until_parked();
14145    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14146    cx.assert_editor_state("obj.abˇb");
14147    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14148
14149    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
14150    cx.update_editor(|editor, window, cx| {
14151        editor.backspace(&Backspace, window, cx);
14152    });
14153    let is_incomplete = false;
14154    handle_completion_request(
14155        "obj.<a|>b",
14156        vec!["a", "ab", "abc"],
14157        is_incomplete,
14158        counter.clone(),
14159        &mut cx,
14160    )
14161    .await;
14162    cx.run_until_parked();
14163    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14164    cx.assert_editor_state("obj.aˇb");
14165    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14166
14167    // Backspace to delete "a" - dismisses menu.
14168    cx.update_editor(|editor, window, cx| {
14169        editor.backspace(&Backspace, window, cx);
14170    });
14171    cx.run_until_parked();
14172    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14173    cx.assert_editor_state("obj.ˇb");
14174    cx.update_editor(|editor, _, _| {
14175        assert_eq!(editor.context_menu_visible(), false);
14176    });
14177}
14178
14179#[gpui::test]
14180async fn test_word_completion(cx: &mut TestAppContext) {
14181    let lsp_fetch_timeout_ms = 10;
14182    init_test(cx, |language_settings| {
14183        language_settings.defaults.completions = Some(CompletionSettingsContent {
14184            words_min_length: Some(0),
14185            lsp_fetch_timeout_ms: Some(10),
14186            lsp_insert_mode: Some(LspInsertMode::Insert),
14187            ..Default::default()
14188        });
14189    });
14190
14191    let mut cx = EditorLspTestContext::new_rust(
14192        lsp::ServerCapabilities {
14193            completion_provider: Some(lsp::CompletionOptions {
14194                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14195                ..lsp::CompletionOptions::default()
14196            }),
14197            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14198            ..lsp::ServerCapabilities::default()
14199        },
14200        cx,
14201    )
14202    .await;
14203
14204    let throttle_completions = Arc::new(AtomicBool::new(false));
14205
14206    let lsp_throttle_completions = throttle_completions.clone();
14207    let _completion_requests_handler =
14208        cx.lsp
14209            .server
14210            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
14211                let lsp_throttle_completions = lsp_throttle_completions.clone();
14212                let cx = cx.clone();
14213                async move {
14214                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
14215                        cx.background_executor()
14216                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
14217                            .await;
14218                    }
14219                    Ok(Some(lsp::CompletionResponse::Array(vec![
14220                        lsp::CompletionItem {
14221                            label: "first".into(),
14222                            ..lsp::CompletionItem::default()
14223                        },
14224                        lsp::CompletionItem {
14225                            label: "last".into(),
14226                            ..lsp::CompletionItem::default()
14227                        },
14228                    ])))
14229                }
14230            });
14231
14232    cx.set_state(indoc! {"
14233        oneˇ
14234        two
14235        three
14236    "});
14237    cx.simulate_keystroke(".");
14238    cx.executor().run_until_parked();
14239    cx.condition(|editor, _| editor.context_menu_visible())
14240        .await;
14241    cx.update_editor(|editor, window, cx| {
14242        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14243        {
14244            assert_eq!(
14245                completion_menu_entries(menu),
14246                &["first", "last"],
14247                "When LSP server is fast to reply, no fallback word completions are used"
14248            );
14249        } else {
14250            panic!("expected completion menu to be open");
14251        }
14252        editor.cancel(&Cancel, window, cx);
14253    });
14254    cx.executor().run_until_parked();
14255    cx.condition(|editor, _| !editor.context_menu_visible())
14256        .await;
14257
14258    throttle_completions.store(true, atomic::Ordering::Release);
14259    cx.simulate_keystroke(".");
14260    cx.executor()
14261        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
14262    cx.executor().run_until_parked();
14263    cx.condition(|editor, _| editor.context_menu_visible())
14264        .await;
14265    cx.update_editor(|editor, _, _| {
14266        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14267        {
14268            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
14269                "When LSP server is slow, document words can be shown instead, if configured accordingly");
14270        } else {
14271            panic!("expected completion menu to be open");
14272        }
14273    });
14274}
14275
14276#[gpui::test]
14277async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
14278    init_test(cx, |language_settings| {
14279        language_settings.defaults.completions = Some(CompletionSettingsContent {
14280            words: Some(WordsCompletionMode::Enabled),
14281            words_min_length: Some(0),
14282            lsp_insert_mode: Some(LspInsertMode::Insert),
14283            ..Default::default()
14284        });
14285    });
14286
14287    let mut cx = EditorLspTestContext::new_rust(
14288        lsp::ServerCapabilities {
14289            completion_provider: Some(lsp::CompletionOptions {
14290                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14291                ..lsp::CompletionOptions::default()
14292            }),
14293            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14294            ..lsp::ServerCapabilities::default()
14295        },
14296        cx,
14297    )
14298    .await;
14299
14300    let _completion_requests_handler =
14301        cx.lsp
14302            .server
14303            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14304                Ok(Some(lsp::CompletionResponse::Array(vec![
14305                    lsp::CompletionItem {
14306                        label: "first".into(),
14307                        ..lsp::CompletionItem::default()
14308                    },
14309                    lsp::CompletionItem {
14310                        label: "last".into(),
14311                        ..lsp::CompletionItem::default()
14312                    },
14313                ])))
14314            });
14315
14316    cx.set_state(indoc! {"ˇ
14317        first
14318        last
14319        second
14320    "});
14321    cx.simulate_keystroke(".");
14322    cx.executor().run_until_parked();
14323    cx.condition(|editor, _| editor.context_menu_visible())
14324        .await;
14325    cx.update_editor(|editor, _, _| {
14326        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14327        {
14328            assert_eq!(
14329                completion_menu_entries(menu),
14330                &["first", "last", "second"],
14331                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
14332            );
14333        } else {
14334            panic!("expected completion menu to be open");
14335        }
14336    });
14337}
14338
14339#[gpui::test]
14340async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
14341    init_test(cx, |language_settings| {
14342        language_settings.defaults.completions = Some(CompletionSettingsContent {
14343            words: Some(WordsCompletionMode::Disabled),
14344            words_min_length: Some(0),
14345            lsp_insert_mode: Some(LspInsertMode::Insert),
14346            ..Default::default()
14347        });
14348    });
14349
14350    let mut cx = EditorLspTestContext::new_rust(
14351        lsp::ServerCapabilities {
14352            completion_provider: Some(lsp::CompletionOptions {
14353                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14354                ..lsp::CompletionOptions::default()
14355            }),
14356            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14357            ..lsp::ServerCapabilities::default()
14358        },
14359        cx,
14360    )
14361    .await;
14362
14363    let _completion_requests_handler =
14364        cx.lsp
14365            .server
14366            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14367                panic!("LSP completions should not be queried when dealing with word completions")
14368            });
14369
14370    cx.set_state(indoc! {"ˇ
14371        first
14372        last
14373        second
14374    "});
14375    cx.update_editor(|editor, window, cx| {
14376        editor.show_word_completions(&ShowWordCompletions, window, cx);
14377    });
14378    cx.executor().run_until_parked();
14379    cx.condition(|editor, _| editor.context_menu_visible())
14380        .await;
14381    cx.update_editor(|editor, _, _| {
14382        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14383        {
14384            assert_eq!(
14385                completion_menu_entries(menu),
14386                &["first", "last", "second"],
14387                "`ShowWordCompletions` action should show word completions"
14388            );
14389        } else {
14390            panic!("expected completion menu to be open");
14391        }
14392    });
14393
14394    cx.simulate_keystroke("l");
14395    cx.executor().run_until_parked();
14396    cx.condition(|editor, _| editor.context_menu_visible())
14397        .await;
14398    cx.update_editor(|editor, _, _| {
14399        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14400        {
14401            assert_eq!(
14402                completion_menu_entries(menu),
14403                &["last"],
14404                "After showing word completions, further editing should filter them and not query the LSP"
14405            );
14406        } else {
14407            panic!("expected completion menu to be open");
14408        }
14409    });
14410}
14411
14412#[gpui::test]
14413async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
14414    init_test(cx, |language_settings| {
14415        language_settings.defaults.completions = Some(CompletionSettingsContent {
14416            words_min_length: Some(0),
14417            lsp: Some(false),
14418            lsp_insert_mode: Some(LspInsertMode::Insert),
14419            ..Default::default()
14420        });
14421    });
14422
14423    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14424
14425    cx.set_state(indoc! {"ˇ
14426        0_usize
14427        let
14428        33
14429        4.5f32
14430    "});
14431    cx.update_editor(|editor, window, cx| {
14432        editor.show_completions(&ShowCompletions::default(), window, cx);
14433    });
14434    cx.executor().run_until_parked();
14435    cx.condition(|editor, _| editor.context_menu_visible())
14436        .await;
14437    cx.update_editor(|editor, window, cx| {
14438        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14439        {
14440            assert_eq!(
14441                completion_menu_entries(menu),
14442                &["let"],
14443                "With no digits in the completion query, no digits should be in the word completions"
14444            );
14445        } else {
14446            panic!("expected completion menu to be open");
14447        }
14448        editor.cancel(&Cancel, window, cx);
14449    });
14450
14451    cx.set_state(indoc! {"14452        0_usize
14453        let
14454        3
14455        33.35f32
14456    "});
14457    cx.update_editor(|editor, window, cx| {
14458        editor.show_completions(&ShowCompletions::default(), window, cx);
14459    });
14460    cx.executor().run_until_parked();
14461    cx.condition(|editor, _| editor.context_menu_visible())
14462        .await;
14463    cx.update_editor(|editor, _, _| {
14464        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14465        {
14466            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
14467                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
14468        } else {
14469            panic!("expected completion menu to be open");
14470        }
14471    });
14472}
14473
14474#[gpui::test]
14475async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
14476    init_test(cx, |language_settings| {
14477        language_settings.defaults.completions = Some(CompletionSettingsContent {
14478            words: Some(WordsCompletionMode::Enabled),
14479            words_min_length: Some(3),
14480            lsp_insert_mode: Some(LspInsertMode::Insert),
14481            ..Default::default()
14482        });
14483    });
14484
14485    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14486    cx.set_state(indoc! {"ˇ
14487        wow
14488        wowen
14489        wowser
14490    "});
14491    cx.simulate_keystroke("w");
14492    cx.executor().run_until_parked();
14493    cx.update_editor(|editor, _, _| {
14494        if editor.context_menu.borrow_mut().is_some() {
14495            panic!(
14496                "expected completion menu to be hidden, as words completion threshold is not met"
14497            );
14498        }
14499    });
14500
14501    cx.update_editor(|editor, window, cx| {
14502        editor.show_word_completions(&ShowWordCompletions, window, cx);
14503    });
14504    cx.executor().run_until_parked();
14505    cx.update_editor(|editor, window, cx| {
14506        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14507        {
14508            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");
14509        } else {
14510            panic!("expected completion menu to be open after the word completions are called with an action");
14511        }
14512
14513        editor.cancel(&Cancel, window, cx);
14514    });
14515    cx.update_editor(|editor, _, _| {
14516        if editor.context_menu.borrow_mut().is_some() {
14517            panic!("expected completion menu to be hidden after canceling");
14518        }
14519    });
14520
14521    cx.simulate_keystroke("o");
14522    cx.executor().run_until_parked();
14523    cx.update_editor(|editor, _, _| {
14524        if editor.context_menu.borrow_mut().is_some() {
14525            panic!(
14526                "expected completion menu to be hidden, as words completion threshold is not met still"
14527            );
14528        }
14529    });
14530
14531    cx.simulate_keystroke("w");
14532    cx.executor().run_until_parked();
14533    cx.update_editor(|editor, _, _| {
14534        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14535        {
14536            assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
14537        } else {
14538            panic!("expected completion menu to be open after the word completions threshold is met");
14539        }
14540    });
14541}
14542
14543#[gpui::test]
14544async fn test_word_completions_disabled(cx: &mut TestAppContext) {
14545    init_test(cx, |language_settings| {
14546        language_settings.defaults.completions = Some(CompletionSettingsContent {
14547            words: Some(WordsCompletionMode::Enabled),
14548            words_min_length: Some(0),
14549            lsp_insert_mode: Some(LspInsertMode::Insert),
14550            ..Default::default()
14551        });
14552    });
14553
14554    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14555    cx.update_editor(|editor, _, _| {
14556        editor.disable_word_completions();
14557    });
14558    cx.set_state(indoc! {"ˇ
14559        wow
14560        wowen
14561        wowser
14562    "});
14563    cx.simulate_keystroke("w");
14564    cx.executor().run_until_parked();
14565    cx.update_editor(|editor, _, _| {
14566        if editor.context_menu.borrow_mut().is_some() {
14567            panic!(
14568                "expected completion menu to be hidden, as words completion are disabled for this editor"
14569            );
14570        }
14571    });
14572
14573    cx.update_editor(|editor, window, cx| {
14574        editor.show_word_completions(&ShowWordCompletions, window, cx);
14575    });
14576    cx.executor().run_until_parked();
14577    cx.update_editor(|editor, _, _| {
14578        if editor.context_menu.borrow_mut().is_some() {
14579            panic!(
14580                "expected completion menu to be hidden even if called for explicitly, as words completion are disabled for this editor"
14581            );
14582        }
14583    });
14584}
14585
14586fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
14587    let position = || lsp::Position {
14588        line: params.text_document_position.position.line,
14589        character: params.text_document_position.position.character,
14590    };
14591    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14592        range: lsp::Range {
14593            start: position(),
14594            end: position(),
14595        },
14596        new_text: text.to_string(),
14597    }))
14598}
14599
14600#[gpui::test]
14601async fn test_multiline_completion(cx: &mut TestAppContext) {
14602    init_test(cx, |_| {});
14603
14604    let fs = FakeFs::new(cx.executor());
14605    fs.insert_tree(
14606        path!("/a"),
14607        json!({
14608            "main.ts": "a",
14609        }),
14610    )
14611    .await;
14612
14613    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14614    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14615    let typescript_language = Arc::new(Language::new(
14616        LanguageConfig {
14617            name: "TypeScript".into(),
14618            matcher: LanguageMatcher {
14619                path_suffixes: vec!["ts".to_string()],
14620                ..LanguageMatcher::default()
14621            },
14622            line_comments: vec!["// ".into()],
14623            ..LanguageConfig::default()
14624        },
14625        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
14626    ));
14627    language_registry.add(typescript_language.clone());
14628    let mut fake_servers = language_registry.register_fake_lsp(
14629        "TypeScript",
14630        FakeLspAdapter {
14631            capabilities: lsp::ServerCapabilities {
14632                completion_provider: Some(lsp::CompletionOptions {
14633                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14634                    ..lsp::CompletionOptions::default()
14635                }),
14636                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14637                ..lsp::ServerCapabilities::default()
14638            },
14639            // Emulate vtsls label generation
14640            label_for_completion: Some(Box::new(|item, _| {
14641                let text = if let Some(description) = item
14642                    .label_details
14643                    .as_ref()
14644                    .and_then(|label_details| label_details.description.as_ref())
14645                {
14646                    format!("{} {}", item.label, description)
14647                } else if let Some(detail) = &item.detail {
14648                    format!("{} {}", item.label, detail)
14649                } else {
14650                    item.label.clone()
14651                };
14652                let len = text.len();
14653                Some(language::CodeLabel {
14654                    text,
14655                    runs: Vec::new(),
14656                    filter_range: 0..len,
14657                })
14658            })),
14659            ..FakeLspAdapter::default()
14660        },
14661    );
14662    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14663    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14664    let worktree_id = workspace
14665        .update(cx, |workspace, _window, cx| {
14666            workspace.project().update(cx, |project, cx| {
14667                project.worktrees(cx).next().unwrap().read(cx).id()
14668            })
14669        })
14670        .unwrap();
14671    let _buffer = project
14672        .update(cx, |project, cx| {
14673            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
14674        })
14675        .await
14676        .unwrap();
14677    let editor = workspace
14678        .update(cx, |workspace, window, cx| {
14679            workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
14680        })
14681        .unwrap()
14682        .await
14683        .unwrap()
14684        .downcast::<Editor>()
14685        .unwrap();
14686    let fake_server = fake_servers.next().await.unwrap();
14687
14688    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
14689    let multiline_label_2 = "a\nb\nc\n";
14690    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
14691    let multiline_description = "d\ne\nf\n";
14692    let multiline_detail_2 = "g\nh\ni\n";
14693
14694    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
14695        move |params, _| async move {
14696            Ok(Some(lsp::CompletionResponse::Array(vec![
14697                lsp::CompletionItem {
14698                    label: multiline_label.to_string(),
14699                    text_edit: gen_text_edit(&params, "new_text_1"),
14700                    ..lsp::CompletionItem::default()
14701                },
14702                lsp::CompletionItem {
14703                    label: "single line label 1".to_string(),
14704                    detail: Some(multiline_detail.to_string()),
14705                    text_edit: gen_text_edit(&params, "new_text_2"),
14706                    ..lsp::CompletionItem::default()
14707                },
14708                lsp::CompletionItem {
14709                    label: "single line label 2".to_string(),
14710                    label_details: Some(lsp::CompletionItemLabelDetails {
14711                        description: Some(multiline_description.to_string()),
14712                        detail: None,
14713                    }),
14714                    text_edit: gen_text_edit(&params, "new_text_2"),
14715                    ..lsp::CompletionItem::default()
14716                },
14717                lsp::CompletionItem {
14718                    label: multiline_label_2.to_string(),
14719                    detail: Some(multiline_detail_2.to_string()),
14720                    text_edit: gen_text_edit(&params, "new_text_3"),
14721                    ..lsp::CompletionItem::default()
14722                },
14723                lsp::CompletionItem {
14724                    label: "Label with many     spaces and \t but without newlines".to_string(),
14725                    detail: Some(
14726                        "Details with many     spaces and \t but without newlines".to_string(),
14727                    ),
14728                    text_edit: gen_text_edit(&params, "new_text_4"),
14729                    ..lsp::CompletionItem::default()
14730                },
14731            ])))
14732        },
14733    );
14734
14735    editor.update_in(cx, |editor, window, cx| {
14736        cx.focus_self(window);
14737        editor.move_to_end(&MoveToEnd, window, cx);
14738        editor.handle_input(".", window, cx);
14739    });
14740    cx.run_until_parked();
14741    completion_handle.next().await.unwrap();
14742
14743    editor.update(cx, |editor, _| {
14744        assert!(editor.context_menu_visible());
14745        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14746        {
14747            let completion_labels = menu
14748                .completions
14749                .borrow()
14750                .iter()
14751                .map(|c| c.label.text.clone())
14752                .collect::<Vec<_>>();
14753            assert_eq!(
14754                completion_labels,
14755                &[
14756                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
14757                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
14758                    "single line label 2 d e f ",
14759                    "a b c g h i ",
14760                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
14761                ],
14762                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
14763            );
14764
14765            for completion in menu
14766                .completions
14767                .borrow()
14768                .iter() {
14769                    assert_eq!(
14770                        completion.label.filter_range,
14771                        0..completion.label.text.len(),
14772                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
14773                    );
14774                }
14775        } else {
14776            panic!("expected completion menu to be open");
14777        }
14778    });
14779}
14780
14781#[gpui::test]
14782async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
14783    init_test(cx, |_| {});
14784    let mut cx = EditorLspTestContext::new_rust(
14785        lsp::ServerCapabilities {
14786            completion_provider: Some(lsp::CompletionOptions {
14787                trigger_characters: Some(vec![".".to_string()]),
14788                ..Default::default()
14789            }),
14790            ..Default::default()
14791        },
14792        cx,
14793    )
14794    .await;
14795    cx.lsp
14796        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14797            Ok(Some(lsp::CompletionResponse::Array(vec![
14798                lsp::CompletionItem {
14799                    label: "first".into(),
14800                    ..Default::default()
14801                },
14802                lsp::CompletionItem {
14803                    label: "last".into(),
14804                    ..Default::default()
14805                },
14806            ])))
14807        });
14808    cx.set_state("variableˇ");
14809    cx.simulate_keystroke(".");
14810    cx.executor().run_until_parked();
14811
14812    cx.update_editor(|editor, _, _| {
14813        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14814        {
14815            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
14816        } else {
14817            panic!("expected completion menu to be open");
14818        }
14819    });
14820
14821    cx.update_editor(|editor, window, cx| {
14822        editor.move_page_down(&MovePageDown::default(), window, cx);
14823        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14824        {
14825            assert!(
14826                menu.selected_item == 1,
14827                "expected PageDown to select the last item from the context menu"
14828            );
14829        } else {
14830            panic!("expected completion menu to stay open after PageDown");
14831        }
14832    });
14833
14834    cx.update_editor(|editor, window, cx| {
14835        editor.move_page_up(&MovePageUp::default(), window, cx);
14836        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14837        {
14838            assert!(
14839                menu.selected_item == 0,
14840                "expected PageUp to select the first item from the context menu"
14841            );
14842        } else {
14843            panic!("expected completion menu to stay open after PageUp");
14844        }
14845    });
14846}
14847
14848#[gpui::test]
14849async fn test_as_is_completions(cx: &mut TestAppContext) {
14850    init_test(cx, |_| {});
14851    let mut cx = EditorLspTestContext::new_rust(
14852        lsp::ServerCapabilities {
14853            completion_provider: Some(lsp::CompletionOptions {
14854                ..Default::default()
14855            }),
14856            ..Default::default()
14857        },
14858        cx,
14859    )
14860    .await;
14861    cx.lsp
14862        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14863            Ok(Some(lsp::CompletionResponse::Array(vec![
14864                lsp::CompletionItem {
14865                    label: "unsafe".into(),
14866                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14867                        range: lsp::Range {
14868                            start: lsp::Position {
14869                                line: 1,
14870                                character: 2,
14871                            },
14872                            end: lsp::Position {
14873                                line: 1,
14874                                character: 3,
14875                            },
14876                        },
14877                        new_text: "unsafe".to_string(),
14878                    })),
14879                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
14880                    ..Default::default()
14881                },
14882            ])))
14883        });
14884    cx.set_state("fn a() {}\n");
14885    cx.executor().run_until_parked();
14886    cx.update_editor(|editor, window, cx| {
14887        editor.show_completions(
14888            &ShowCompletions {
14889                trigger: Some("\n".into()),
14890            },
14891            window,
14892            cx,
14893        );
14894    });
14895    cx.executor().run_until_parked();
14896
14897    cx.update_editor(|editor, window, cx| {
14898        editor.confirm_completion(&Default::default(), window, cx)
14899    });
14900    cx.executor().run_until_parked();
14901    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
14902}
14903
14904#[gpui::test]
14905async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
14906    init_test(cx, |_| {});
14907    let language =
14908        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
14909    let mut cx = EditorLspTestContext::new(
14910        language,
14911        lsp::ServerCapabilities {
14912            completion_provider: Some(lsp::CompletionOptions {
14913                ..lsp::CompletionOptions::default()
14914            }),
14915            ..lsp::ServerCapabilities::default()
14916        },
14917        cx,
14918    )
14919    .await;
14920
14921    cx.set_state(
14922        "#ifndef BAR_H
14923#define BAR_H
14924
14925#include <stdbool.h>
14926
14927int fn_branch(bool do_branch1, bool do_branch2);
14928
14929#endif // BAR_H
14930ˇ",
14931    );
14932    cx.executor().run_until_parked();
14933    cx.update_editor(|editor, window, cx| {
14934        editor.handle_input("#", window, cx);
14935    });
14936    cx.executor().run_until_parked();
14937    cx.update_editor(|editor, window, cx| {
14938        editor.handle_input("i", window, cx);
14939    });
14940    cx.executor().run_until_parked();
14941    cx.update_editor(|editor, window, cx| {
14942        editor.handle_input("n", window, cx);
14943    });
14944    cx.executor().run_until_parked();
14945    cx.assert_editor_state(
14946        "#ifndef BAR_H
14947#define BAR_H
14948
14949#include <stdbool.h>
14950
14951int fn_branch(bool do_branch1, bool do_branch2);
14952
14953#endif // BAR_H
14954#inˇ",
14955    );
14956
14957    cx.lsp
14958        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14959            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
14960                is_incomplete: false,
14961                item_defaults: None,
14962                items: vec![lsp::CompletionItem {
14963                    kind: Some(lsp::CompletionItemKind::SNIPPET),
14964                    label_details: Some(lsp::CompletionItemLabelDetails {
14965                        detail: Some("header".to_string()),
14966                        description: None,
14967                    }),
14968                    label: " include".to_string(),
14969                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14970                        range: lsp::Range {
14971                            start: lsp::Position {
14972                                line: 8,
14973                                character: 1,
14974                            },
14975                            end: lsp::Position {
14976                                line: 8,
14977                                character: 1,
14978                            },
14979                        },
14980                        new_text: "include \"$0\"".to_string(),
14981                    })),
14982                    sort_text: Some("40b67681include".to_string()),
14983                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
14984                    filter_text: Some("include".to_string()),
14985                    insert_text: Some("include \"$0\"".to_string()),
14986                    ..lsp::CompletionItem::default()
14987                }],
14988            })))
14989        });
14990    cx.update_editor(|editor, window, cx| {
14991        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14992    });
14993    cx.executor().run_until_parked();
14994    cx.update_editor(|editor, window, cx| {
14995        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
14996    });
14997    cx.executor().run_until_parked();
14998    cx.assert_editor_state(
14999        "#ifndef BAR_H
15000#define BAR_H
15001
15002#include <stdbool.h>
15003
15004int fn_branch(bool do_branch1, bool do_branch2);
15005
15006#endif // BAR_H
15007#include \"ˇ\"",
15008    );
15009
15010    cx.lsp
15011        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15012            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15013                is_incomplete: true,
15014                item_defaults: None,
15015                items: vec![lsp::CompletionItem {
15016                    kind: Some(lsp::CompletionItemKind::FILE),
15017                    label: "AGL/".to_string(),
15018                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15019                        range: lsp::Range {
15020                            start: lsp::Position {
15021                                line: 8,
15022                                character: 10,
15023                            },
15024                            end: lsp::Position {
15025                                line: 8,
15026                                character: 11,
15027                            },
15028                        },
15029                        new_text: "AGL/".to_string(),
15030                    })),
15031                    sort_text: Some("40b67681AGL/".to_string()),
15032                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
15033                    filter_text: Some("AGL/".to_string()),
15034                    insert_text: Some("AGL/".to_string()),
15035                    ..lsp::CompletionItem::default()
15036                }],
15037            })))
15038        });
15039    cx.update_editor(|editor, window, cx| {
15040        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15041    });
15042    cx.executor().run_until_parked();
15043    cx.update_editor(|editor, window, cx| {
15044        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15045    });
15046    cx.executor().run_until_parked();
15047    cx.assert_editor_state(
15048        r##"#ifndef BAR_H
15049#define BAR_H
15050
15051#include <stdbool.h>
15052
15053int fn_branch(bool do_branch1, bool do_branch2);
15054
15055#endif // BAR_H
15056#include "AGL/ˇ"##,
15057    );
15058
15059    cx.update_editor(|editor, window, cx| {
15060        editor.handle_input("\"", window, cx);
15061    });
15062    cx.executor().run_until_parked();
15063    cx.assert_editor_state(
15064        r##"#ifndef BAR_H
15065#define BAR_H
15066
15067#include <stdbool.h>
15068
15069int fn_branch(bool do_branch1, bool do_branch2);
15070
15071#endif // BAR_H
15072#include "AGL/"ˇ"##,
15073    );
15074}
15075
15076#[gpui::test]
15077async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
15078    init_test(cx, |_| {});
15079
15080    let mut cx = EditorLspTestContext::new_rust(
15081        lsp::ServerCapabilities {
15082            completion_provider: Some(lsp::CompletionOptions {
15083                trigger_characters: Some(vec![".".to_string()]),
15084                resolve_provider: Some(true),
15085                ..Default::default()
15086            }),
15087            ..Default::default()
15088        },
15089        cx,
15090    )
15091    .await;
15092
15093    cx.set_state("fn main() { let a = 2ˇ; }");
15094    cx.simulate_keystroke(".");
15095    let completion_item = lsp::CompletionItem {
15096        label: "Some".into(),
15097        kind: Some(lsp::CompletionItemKind::SNIPPET),
15098        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15099        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15100            kind: lsp::MarkupKind::Markdown,
15101            value: "```rust\nSome(2)\n```".to_string(),
15102        })),
15103        deprecated: Some(false),
15104        sort_text: Some("Some".to_string()),
15105        filter_text: Some("Some".to_string()),
15106        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15107        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15108            range: lsp::Range {
15109                start: lsp::Position {
15110                    line: 0,
15111                    character: 22,
15112                },
15113                end: lsp::Position {
15114                    line: 0,
15115                    character: 22,
15116                },
15117            },
15118            new_text: "Some(2)".to_string(),
15119        })),
15120        additional_text_edits: Some(vec![lsp::TextEdit {
15121            range: lsp::Range {
15122                start: lsp::Position {
15123                    line: 0,
15124                    character: 20,
15125                },
15126                end: lsp::Position {
15127                    line: 0,
15128                    character: 22,
15129                },
15130            },
15131            new_text: "".to_string(),
15132        }]),
15133        ..Default::default()
15134    };
15135
15136    let closure_completion_item = completion_item.clone();
15137    let counter = Arc::new(AtomicUsize::new(0));
15138    let counter_clone = counter.clone();
15139    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15140        let task_completion_item = closure_completion_item.clone();
15141        counter_clone.fetch_add(1, atomic::Ordering::Release);
15142        async move {
15143            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15144                is_incomplete: true,
15145                item_defaults: None,
15146                items: vec![task_completion_item],
15147            })))
15148        }
15149    });
15150
15151    cx.condition(|editor, _| editor.context_menu_visible())
15152        .await;
15153    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
15154    assert!(request.next().await.is_some());
15155    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15156
15157    cx.simulate_keystrokes("S o m");
15158    cx.condition(|editor, _| editor.context_menu_visible())
15159        .await;
15160    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
15161    assert!(request.next().await.is_some());
15162    assert!(request.next().await.is_some());
15163    assert!(request.next().await.is_some());
15164    request.close();
15165    assert!(request.next().await.is_none());
15166    assert_eq!(
15167        counter.load(atomic::Ordering::Acquire),
15168        4,
15169        "With the completions menu open, only one LSP request should happen per input"
15170    );
15171}
15172
15173#[gpui::test]
15174async fn test_toggle_comment(cx: &mut TestAppContext) {
15175    init_test(cx, |_| {});
15176    let mut cx = EditorTestContext::new(cx).await;
15177    let language = Arc::new(Language::new(
15178        LanguageConfig {
15179            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15180            ..Default::default()
15181        },
15182        Some(tree_sitter_rust::LANGUAGE.into()),
15183    ));
15184    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15185
15186    // If multiple selections intersect a line, the line is only toggled once.
15187    cx.set_state(indoc! {"
15188        fn a() {
15189            «//b();
15190            ˇ»// «c();
15191            //ˇ»  d();
15192        }
15193    "});
15194
15195    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15196
15197    cx.assert_editor_state(indoc! {"
15198        fn a() {
15199            «b();
15200            c();
15201            ˇ» d();
15202        }
15203    "});
15204
15205    // The comment prefix is inserted at the same column for every line in a
15206    // selection.
15207    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15208
15209    cx.assert_editor_state(indoc! {"
15210        fn a() {
15211            // «b();
15212            // c();
15213            ˇ»//  d();
15214        }
15215    "});
15216
15217    // If a selection ends at the beginning of a line, that line is not toggled.
15218    cx.set_selections_state(indoc! {"
15219        fn a() {
15220            // b();
15221            «// c();
15222        ˇ»    //  d();
15223        }
15224    "});
15225
15226    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15227
15228    cx.assert_editor_state(indoc! {"
15229        fn a() {
15230            // b();
15231            «c();
15232        ˇ»    //  d();
15233        }
15234    "});
15235
15236    // If a selection span a single line and is empty, the line is toggled.
15237    cx.set_state(indoc! {"
15238        fn a() {
15239            a();
15240            b();
15241        ˇ
15242        }
15243    "});
15244
15245    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15246
15247    cx.assert_editor_state(indoc! {"
15248        fn a() {
15249            a();
15250            b();
15251        //•ˇ
15252        }
15253    "});
15254
15255    // If a selection span multiple lines, empty lines are not toggled.
15256    cx.set_state(indoc! {"
15257        fn a() {
15258            «a();
15259
15260            c();ˇ»
15261        }
15262    "});
15263
15264    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15265
15266    cx.assert_editor_state(indoc! {"
15267        fn a() {
15268            // «a();
15269
15270            // c();ˇ»
15271        }
15272    "});
15273
15274    // If a selection includes multiple comment prefixes, all lines are uncommented.
15275    cx.set_state(indoc! {"
15276        fn a() {
15277            «// a();
15278            /// b();
15279            //! c();ˇ»
15280        }
15281    "});
15282
15283    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15284
15285    cx.assert_editor_state(indoc! {"
15286        fn a() {
15287            «a();
15288            b();
15289            c();ˇ»
15290        }
15291    "});
15292}
15293
15294#[gpui::test]
15295async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
15296    init_test(cx, |_| {});
15297    let mut cx = EditorTestContext::new(cx).await;
15298    let language = Arc::new(Language::new(
15299        LanguageConfig {
15300            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15301            ..Default::default()
15302        },
15303        Some(tree_sitter_rust::LANGUAGE.into()),
15304    ));
15305    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15306
15307    let toggle_comments = &ToggleComments {
15308        advance_downwards: false,
15309        ignore_indent: true,
15310    };
15311
15312    // If multiple selections intersect a line, the line is only toggled once.
15313    cx.set_state(indoc! {"
15314        fn a() {
15315        //    «b();
15316        //    c();
15317        //    ˇ» d();
15318        }
15319    "});
15320
15321    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15322
15323    cx.assert_editor_state(indoc! {"
15324        fn a() {
15325            «b();
15326            c();
15327            ˇ» d();
15328        }
15329    "});
15330
15331    // The comment prefix is inserted at the beginning of each line
15332    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15333
15334    cx.assert_editor_state(indoc! {"
15335        fn a() {
15336        //    «b();
15337        //    c();
15338        //    ˇ» d();
15339        }
15340    "});
15341
15342    // If a selection ends at the beginning of a line, that line is not toggled.
15343    cx.set_selections_state(indoc! {"
15344        fn a() {
15345        //    b();
15346        //    «c();
15347        ˇ»//     d();
15348        }
15349    "});
15350
15351    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15352
15353    cx.assert_editor_state(indoc! {"
15354        fn a() {
15355        //    b();
15356            «c();
15357        ˇ»//     d();
15358        }
15359    "});
15360
15361    // If a selection span a single line and is empty, the line is toggled.
15362    cx.set_state(indoc! {"
15363        fn a() {
15364            a();
15365            b();
15366        ˇ
15367        }
15368    "});
15369
15370    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15371
15372    cx.assert_editor_state(indoc! {"
15373        fn a() {
15374            a();
15375            b();
15376        //ˇ
15377        }
15378    "});
15379
15380    // If a selection span multiple lines, empty lines are not toggled.
15381    cx.set_state(indoc! {"
15382        fn a() {
15383            «a();
15384
15385            c();ˇ»
15386        }
15387    "});
15388
15389    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15390
15391    cx.assert_editor_state(indoc! {"
15392        fn a() {
15393        //    «a();
15394
15395        //    c();ˇ»
15396        }
15397    "});
15398
15399    // If a selection includes multiple comment prefixes, all lines are uncommented.
15400    cx.set_state(indoc! {"
15401        fn a() {
15402        //    «a();
15403        ///    b();
15404        //!    c();ˇ»
15405        }
15406    "});
15407
15408    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15409
15410    cx.assert_editor_state(indoc! {"
15411        fn a() {
15412            «a();
15413            b();
15414            c();ˇ»
15415        }
15416    "});
15417}
15418
15419#[gpui::test]
15420async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
15421    init_test(cx, |_| {});
15422
15423    let language = Arc::new(Language::new(
15424        LanguageConfig {
15425            line_comments: vec!["// ".into()],
15426            ..Default::default()
15427        },
15428        Some(tree_sitter_rust::LANGUAGE.into()),
15429    ));
15430
15431    let mut cx = EditorTestContext::new(cx).await;
15432
15433    cx.language_registry().add(language.clone());
15434    cx.update_buffer(|buffer, cx| {
15435        buffer.set_language(Some(language), cx);
15436    });
15437
15438    let toggle_comments = &ToggleComments {
15439        advance_downwards: true,
15440        ignore_indent: false,
15441    };
15442
15443    // Single cursor on one line -> advance
15444    // Cursor moves horizontally 3 characters as well on non-blank line
15445    cx.set_state(indoc!(
15446        "fn a() {
15447             ˇdog();
15448             cat();
15449        }"
15450    ));
15451    cx.update_editor(|editor, window, cx| {
15452        editor.toggle_comments(toggle_comments, window, cx);
15453    });
15454    cx.assert_editor_state(indoc!(
15455        "fn a() {
15456             // dog();
15457             catˇ();
15458        }"
15459    ));
15460
15461    // Single selection on one line -> don't advance
15462    cx.set_state(indoc!(
15463        "fn a() {
15464             «dog()ˇ»;
15465             cat();
15466        }"
15467    ));
15468    cx.update_editor(|editor, window, cx| {
15469        editor.toggle_comments(toggle_comments, window, cx);
15470    });
15471    cx.assert_editor_state(indoc!(
15472        "fn a() {
15473             // «dog()ˇ»;
15474             cat();
15475        }"
15476    ));
15477
15478    // Multiple cursors on one line -> advance
15479    cx.set_state(indoc!(
15480        "fn a() {
15481             ˇdˇog();
15482             cat();
15483        }"
15484    ));
15485    cx.update_editor(|editor, window, cx| {
15486        editor.toggle_comments(toggle_comments, window, cx);
15487    });
15488    cx.assert_editor_state(indoc!(
15489        "fn a() {
15490             // dog();
15491             catˇ(ˇ);
15492        }"
15493    ));
15494
15495    // Multiple cursors on one line, with selection -> don't advance
15496    cx.set_state(indoc!(
15497        "fn a() {
15498             ˇdˇog«()ˇ»;
15499             cat();
15500        }"
15501    ));
15502    cx.update_editor(|editor, window, cx| {
15503        editor.toggle_comments(toggle_comments, window, cx);
15504    });
15505    cx.assert_editor_state(indoc!(
15506        "fn a() {
15507             // ˇdˇog«()ˇ»;
15508             cat();
15509        }"
15510    ));
15511
15512    // Single cursor on one line -> advance
15513    // Cursor moves to column 0 on blank line
15514    cx.set_state(indoc!(
15515        "fn a() {
15516             ˇdog();
15517
15518             cat();
15519        }"
15520    ));
15521    cx.update_editor(|editor, window, cx| {
15522        editor.toggle_comments(toggle_comments, window, cx);
15523    });
15524    cx.assert_editor_state(indoc!(
15525        "fn a() {
15526             // dog();
15527        ˇ
15528             cat();
15529        }"
15530    ));
15531
15532    // Single cursor on one line -> advance
15533    // Cursor starts and ends at column 0
15534    cx.set_state(indoc!(
15535        "fn a() {
15536         ˇ    dog();
15537             cat();
15538        }"
15539    ));
15540    cx.update_editor(|editor, window, cx| {
15541        editor.toggle_comments(toggle_comments, window, cx);
15542    });
15543    cx.assert_editor_state(indoc!(
15544        "fn a() {
15545             // dog();
15546         ˇ    cat();
15547        }"
15548    ));
15549}
15550
15551#[gpui::test]
15552async fn test_toggle_block_comment(cx: &mut TestAppContext) {
15553    init_test(cx, |_| {});
15554
15555    let mut cx = EditorTestContext::new(cx).await;
15556
15557    let html_language = Arc::new(
15558        Language::new(
15559            LanguageConfig {
15560                name: "HTML".into(),
15561                block_comment: Some(BlockCommentConfig {
15562                    start: "<!-- ".into(),
15563                    prefix: "".into(),
15564                    end: " -->".into(),
15565                    tab_size: 0,
15566                }),
15567                ..Default::default()
15568            },
15569            Some(tree_sitter_html::LANGUAGE.into()),
15570        )
15571        .with_injection_query(
15572            r#"
15573            (script_element
15574                (raw_text) @injection.content
15575                (#set! injection.language "javascript"))
15576            "#,
15577        )
15578        .unwrap(),
15579    );
15580
15581    let javascript_language = Arc::new(Language::new(
15582        LanguageConfig {
15583            name: "JavaScript".into(),
15584            line_comments: vec!["// ".into()],
15585            ..Default::default()
15586        },
15587        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
15588    ));
15589
15590    cx.language_registry().add(html_language.clone());
15591    cx.language_registry().add(javascript_language);
15592    cx.update_buffer(|buffer, cx| {
15593        buffer.set_language(Some(html_language), cx);
15594    });
15595
15596    // Toggle comments for empty selections
15597    cx.set_state(
15598        &r#"
15599            <p>A</p>ˇ
15600            <p>B</p>ˇ
15601            <p>C</p>ˇ
15602        "#
15603        .unindent(),
15604    );
15605    cx.update_editor(|editor, window, cx| {
15606        editor.toggle_comments(&ToggleComments::default(), window, cx)
15607    });
15608    cx.assert_editor_state(
15609        &r#"
15610            <!-- <p>A</p>ˇ -->
15611            <!-- <p>B</p>ˇ -->
15612            <!-- <p>C</p>ˇ -->
15613        "#
15614        .unindent(),
15615    );
15616    cx.update_editor(|editor, window, cx| {
15617        editor.toggle_comments(&ToggleComments::default(), window, cx)
15618    });
15619    cx.assert_editor_state(
15620        &r#"
15621            <p>A</p>ˇ
15622            <p>B</p>ˇ
15623            <p>C</p>ˇ
15624        "#
15625        .unindent(),
15626    );
15627
15628    // Toggle comments for mixture of empty and non-empty selections, where
15629    // multiple selections occupy a given line.
15630    cx.set_state(
15631        &r#"
15632            <p>A«</p>
15633            <p>ˇ»B</p>ˇ
15634            <p>C«</p>
15635            <p>ˇ»D</p>ˇ
15636        "#
15637        .unindent(),
15638    );
15639
15640    cx.update_editor(|editor, window, cx| {
15641        editor.toggle_comments(&ToggleComments::default(), window, cx)
15642    });
15643    cx.assert_editor_state(
15644        &r#"
15645            <!-- <p>A«</p>
15646            <p>ˇ»B</p>ˇ -->
15647            <!-- <p>C«</p>
15648            <p>ˇ»D</p>ˇ -->
15649        "#
15650        .unindent(),
15651    );
15652    cx.update_editor(|editor, window, cx| {
15653        editor.toggle_comments(&ToggleComments::default(), window, cx)
15654    });
15655    cx.assert_editor_state(
15656        &r#"
15657            <p>A«</p>
15658            <p>ˇ»B</p>ˇ
15659            <p>C«</p>
15660            <p>ˇ»D</p>ˇ
15661        "#
15662        .unindent(),
15663    );
15664
15665    // Toggle comments when different languages are active for different
15666    // selections.
15667    cx.set_state(
15668        &r#"
15669            ˇ<script>
15670                ˇvar x = new Y();
15671            ˇ</script>
15672        "#
15673        .unindent(),
15674    );
15675    cx.executor().run_until_parked();
15676    cx.update_editor(|editor, window, cx| {
15677        editor.toggle_comments(&ToggleComments::default(), window, cx)
15678    });
15679    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
15680    // Uncommenting and commenting from this position brings in even more wrong artifacts.
15681    cx.assert_editor_state(
15682        &r#"
15683            <!-- ˇ<script> -->
15684                // ˇvar x = new Y();
15685            <!-- ˇ</script> -->
15686        "#
15687        .unindent(),
15688    );
15689}
15690
15691#[gpui::test]
15692fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
15693    init_test(cx, |_| {});
15694
15695    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15696    let multibuffer = cx.new(|cx| {
15697        let mut multibuffer = MultiBuffer::new(ReadWrite);
15698        multibuffer.push_excerpts(
15699            buffer.clone(),
15700            [
15701                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
15702                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
15703            ],
15704            cx,
15705        );
15706        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
15707        multibuffer
15708    });
15709
15710    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
15711    editor.update_in(cx, |editor, window, cx| {
15712        assert_eq!(editor.text(cx), "aaaa\nbbbb");
15713        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15714            s.select_ranges([
15715                Point::new(0, 0)..Point::new(0, 0),
15716                Point::new(1, 0)..Point::new(1, 0),
15717            ])
15718        });
15719
15720        editor.handle_input("X", window, cx);
15721        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
15722        assert_eq!(
15723            editor.selections.ranges(cx),
15724            [
15725                Point::new(0, 1)..Point::new(0, 1),
15726                Point::new(1, 1)..Point::new(1, 1),
15727            ]
15728        );
15729
15730        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
15731        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15732            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
15733        });
15734        editor.backspace(&Default::default(), window, cx);
15735        assert_eq!(editor.text(cx), "Xa\nbbb");
15736        assert_eq!(
15737            editor.selections.ranges(cx),
15738            [Point::new(1, 0)..Point::new(1, 0)]
15739        );
15740
15741        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15742            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
15743        });
15744        editor.backspace(&Default::default(), window, cx);
15745        assert_eq!(editor.text(cx), "X\nbb");
15746        assert_eq!(
15747            editor.selections.ranges(cx),
15748            [Point::new(0, 1)..Point::new(0, 1)]
15749        );
15750    });
15751}
15752
15753#[gpui::test]
15754fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
15755    init_test(cx, |_| {});
15756
15757    let markers = vec![('[', ']').into(), ('(', ')').into()];
15758    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
15759        indoc! {"
15760            [aaaa
15761            (bbbb]
15762            cccc)",
15763        },
15764        markers.clone(),
15765    );
15766    let excerpt_ranges = markers.into_iter().map(|marker| {
15767        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
15768        ExcerptRange::new(context)
15769    });
15770    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
15771    let multibuffer = cx.new(|cx| {
15772        let mut multibuffer = MultiBuffer::new(ReadWrite);
15773        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
15774        multibuffer
15775    });
15776
15777    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
15778    editor.update_in(cx, |editor, window, cx| {
15779        let (expected_text, selection_ranges) = marked_text_ranges(
15780            indoc! {"
15781                aaaa
15782                bˇbbb
15783                bˇbbˇb
15784                cccc"
15785            },
15786            true,
15787        );
15788        assert_eq!(editor.text(cx), expected_text);
15789        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15790            s.select_ranges(selection_ranges)
15791        });
15792
15793        editor.handle_input("X", window, cx);
15794
15795        let (expected_text, expected_selections) = marked_text_ranges(
15796            indoc! {"
15797                aaaa
15798                bXˇbbXb
15799                bXˇbbXˇb
15800                cccc"
15801            },
15802            false,
15803        );
15804        assert_eq!(editor.text(cx), expected_text);
15805        assert_eq!(editor.selections.ranges(cx), expected_selections);
15806
15807        editor.newline(&Newline, window, cx);
15808        let (expected_text, expected_selections) = marked_text_ranges(
15809            indoc! {"
15810                aaaa
15811                bX
15812                ˇbbX
15813                b
15814                bX
15815                ˇbbX
15816                ˇb
15817                cccc"
15818            },
15819            false,
15820        );
15821        assert_eq!(editor.text(cx), expected_text);
15822        assert_eq!(editor.selections.ranges(cx), expected_selections);
15823    });
15824}
15825
15826#[gpui::test]
15827fn test_refresh_selections(cx: &mut TestAppContext) {
15828    init_test(cx, |_| {});
15829
15830    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15831    let mut excerpt1_id = None;
15832    let multibuffer = cx.new(|cx| {
15833        let mut multibuffer = MultiBuffer::new(ReadWrite);
15834        excerpt1_id = multibuffer
15835            .push_excerpts(
15836                buffer.clone(),
15837                [
15838                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
15839                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
15840                ],
15841                cx,
15842            )
15843            .into_iter()
15844            .next();
15845        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
15846        multibuffer
15847    });
15848
15849    let editor = cx.add_window(|window, cx| {
15850        let mut editor = build_editor(multibuffer.clone(), window, cx);
15851        let snapshot = editor.snapshot(window, cx);
15852        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15853            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
15854        });
15855        editor.begin_selection(
15856            Point::new(2, 1).to_display_point(&snapshot),
15857            true,
15858            1,
15859            window,
15860            cx,
15861        );
15862        assert_eq!(
15863            editor.selections.ranges(cx),
15864            [
15865                Point::new(1, 3)..Point::new(1, 3),
15866                Point::new(2, 1)..Point::new(2, 1),
15867            ]
15868        );
15869        editor
15870    });
15871
15872    // Refreshing selections is a no-op when excerpts haven't changed.
15873    _ = editor.update(cx, |editor, window, cx| {
15874        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
15875        assert_eq!(
15876            editor.selections.ranges(cx),
15877            [
15878                Point::new(1, 3)..Point::new(1, 3),
15879                Point::new(2, 1)..Point::new(2, 1),
15880            ]
15881        );
15882    });
15883
15884    multibuffer.update(cx, |multibuffer, cx| {
15885        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
15886    });
15887    _ = editor.update(cx, |editor, window, cx| {
15888        // Removing an excerpt causes the first selection to become degenerate.
15889        assert_eq!(
15890            editor.selections.ranges(cx),
15891            [
15892                Point::new(0, 0)..Point::new(0, 0),
15893                Point::new(0, 1)..Point::new(0, 1)
15894            ]
15895        );
15896
15897        // Refreshing selections will relocate the first selection to the original buffer
15898        // location.
15899        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
15900        assert_eq!(
15901            editor.selections.ranges(cx),
15902            [
15903                Point::new(0, 1)..Point::new(0, 1),
15904                Point::new(0, 3)..Point::new(0, 3)
15905            ]
15906        );
15907        assert!(editor.selections.pending_anchor().is_some());
15908    });
15909}
15910
15911#[gpui::test]
15912fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
15913    init_test(cx, |_| {});
15914
15915    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15916    let mut excerpt1_id = None;
15917    let multibuffer = cx.new(|cx| {
15918        let mut multibuffer = MultiBuffer::new(ReadWrite);
15919        excerpt1_id = multibuffer
15920            .push_excerpts(
15921                buffer.clone(),
15922                [
15923                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
15924                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
15925                ],
15926                cx,
15927            )
15928            .into_iter()
15929            .next();
15930        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
15931        multibuffer
15932    });
15933
15934    let editor = cx.add_window(|window, cx| {
15935        let mut editor = build_editor(multibuffer.clone(), window, cx);
15936        let snapshot = editor.snapshot(window, cx);
15937        editor.begin_selection(
15938            Point::new(1, 3).to_display_point(&snapshot),
15939            false,
15940            1,
15941            window,
15942            cx,
15943        );
15944        assert_eq!(
15945            editor.selections.ranges(cx),
15946            [Point::new(1, 3)..Point::new(1, 3)]
15947        );
15948        editor
15949    });
15950
15951    multibuffer.update(cx, |multibuffer, cx| {
15952        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
15953    });
15954    _ = editor.update(cx, |editor, window, cx| {
15955        assert_eq!(
15956            editor.selections.ranges(cx),
15957            [Point::new(0, 0)..Point::new(0, 0)]
15958        );
15959
15960        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
15961        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
15962        assert_eq!(
15963            editor.selections.ranges(cx),
15964            [Point::new(0, 3)..Point::new(0, 3)]
15965        );
15966        assert!(editor.selections.pending_anchor().is_some());
15967    });
15968}
15969
15970#[gpui::test]
15971async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
15972    init_test(cx, |_| {});
15973
15974    let language = Arc::new(
15975        Language::new(
15976            LanguageConfig {
15977                brackets: BracketPairConfig {
15978                    pairs: vec![
15979                        BracketPair {
15980                            start: "{".to_string(),
15981                            end: "}".to_string(),
15982                            close: true,
15983                            surround: true,
15984                            newline: true,
15985                        },
15986                        BracketPair {
15987                            start: "/* ".to_string(),
15988                            end: " */".to_string(),
15989                            close: true,
15990                            surround: true,
15991                            newline: true,
15992                        },
15993                    ],
15994                    ..Default::default()
15995                },
15996                ..Default::default()
15997            },
15998            Some(tree_sitter_rust::LANGUAGE.into()),
15999        )
16000        .with_indents_query("")
16001        .unwrap(),
16002    );
16003
16004    let text = concat!(
16005        "{   }\n",     //
16006        "  x\n",       //
16007        "  /*   */\n", //
16008        "x\n",         //
16009        "{{} }\n",     //
16010    );
16011
16012    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
16013    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
16014    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
16015    editor
16016        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
16017        .await;
16018
16019    editor.update_in(cx, |editor, window, cx| {
16020        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16021            s.select_display_ranges([
16022                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
16023                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
16024                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
16025            ])
16026        });
16027        editor.newline(&Newline, window, cx);
16028
16029        assert_eq!(
16030            editor.buffer().read(cx).read(cx).text(),
16031            concat!(
16032                "{ \n",    // Suppress rustfmt
16033                "\n",      //
16034                "}\n",     //
16035                "  x\n",   //
16036                "  /* \n", //
16037                "  \n",    //
16038                "  */\n",  //
16039                "x\n",     //
16040                "{{} \n",  //
16041                "}\n",     //
16042            )
16043        );
16044    });
16045}
16046
16047#[gpui::test]
16048fn test_highlighted_ranges(cx: &mut TestAppContext) {
16049    init_test(cx, |_| {});
16050
16051    let editor = cx.add_window(|window, cx| {
16052        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
16053        build_editor(buffer, window, cx)
16054    });
16055
16056    _ = editor.update(cx, |editor, window, cx| {
16057        struct Type1;
16058        struct Type2;
16059
16060        let buffer = editor.buffer.read(cx).snapshot(cx);
16061
16062        let anchor_range =
16063            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
16064
16065        editor.highlight_background::<Type1>(
16066            &[
16067                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
16068                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
16069                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
16070                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
16071            ],
16072            |_| Hsla::red(),
16073            cx,
16074        );
16075        editor.highlight_background::<Type2>(
16076            &[
16077                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
16078                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
16079                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
16080                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
16081            ],
16082            |_| Hsla::green(),
16083            cx,
16084        );
16085
16086        let snapshot = editor.snapshot(window, cx);
16087        let highlighted_ranges = editor.sorted_background_highlights_in_range(
16088            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
16089            &snapshot,
16090            cx.theme(),
16091        );
16092        assert_eq!(
16093            highlighted_ranges,
16094            &[
16095                (
16096                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
16097                    Hsla::green(),
16098                ),
16099                (
16100                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
16101                    Hsla::red(),
16102                ),
16103                (
16104                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
16105                    Hsla::green(),
16106                ),
16107                (
16108                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16109                    Hsla::red(),
16110                ),
16111            ]
16112        );
16113        assert_eq!(
16114            editor.sorted_background_highlights_in_range(
16115                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
16116                &snapshot,
16117                cx.theme(),
16118            ),
16119            &[(
16120                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16121                Hsla::red(),
16122            )]
16123        );
16124    });
16125}
16126
16127#[gpui::test]
16128async fn test_following(cx: &mut TestAppContext) {
16129    init_test(cx, |_| {});
16130
16131    let fs = FakeFs::new(cx.executor());
16132    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16133
16134    let buffer = project.update(cx, |project, cx| {
16135        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, false, cx);
16136        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
16137    });
16138    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
16139    let follower = cx.update(|cx| {
16140        cx.open_window(
16141            WindowOptions {
16142                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
16143                    gpui::Point::new(px(0.), px(0.)),
16144                    gpui::Point::new(px(10.), px(80.)),
16145                ))),
16146                ..Default::default()
16147            },
16148            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
16149        )
16150        .unwrap()
16151    });
16152
16153    let is_still_following = Rc::new(RefCell::new(true));
16154    let follower_edit_event_count = Rc::new(RefCell::new(0));
16155    let pending_update = Rc::new(RefCell::new(None));
16156    let leader_entity = leader.root(cx).unwrap();
16157    let follower_entity = follower.root(cx).unwrap();
16158    _ = follower.update(cx, {
16159        let update = pending_update.clone();
16160        let is_still_following = is_still_following.clone();
16161        let follower_edit_event_count = follower_edit_event_count.clone();
16162        |_, window, cx| {
16163            cx.subscribe_in(
16164                &leader_entity,
16165                window,
16166                move |_, leader, event, window, cx| {
16167                    leader.read(cx).add_event_to_update_proto(
16168                        event,
16169                        &mut update.borrow_mut(),
16170                        window,
16171                        cx,
16172                    );
16173                },
16174            )
16175            .detach();
16176
16177            cx.subscribe_in(
16178                &follower_entity,
16179                window,
16180                move |_, _, event: &EditorEvent, _window, _cx| {
16181                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
16182                        *is_still_following.borrow_mut() = false;
16183                    }
16184
16185                    if let EditorEvent::BufferEdited = event {
16186                        *follower_edit_event_count.borrow_mut() += 1;
16187                    }
16188                },
16189            )
16190            .detach();
16191        }
16192    });
16193
16194    // Update the selections only
16195    _ = leader.update(cx, |leader, window, cx| {
16196        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16197            s.select_ranges([1..1])
16198        });
16199    });
16200    follower
16201        .update(cx, |follower, window, cx| {
16202            follower.apply_update_proto(
16203                &project,
16204                pending_update.borrow_mut().take().unwrap(),
16205                window,
16206                cx,
16207            )
16208        })
16209        .unwrap()
16210        .await
16211        .unwrap();
16212    _ = follower.update(cx, |follower, _, cx| {
16213        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
16214    });
16215    assert!(*is_still_following.borrow());
16216    assert_eq!(*follower_edit_event_count.borrow(), 0);
16217
16218    // Update the scroll position only
16219    _ = leader.update(cx, |leader, window, cx| {
16220        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16221    });
16222    follower
16223        .update(cx, |follower, window, cx| {
16224            follower.apply_update_proto(
16225                &project,
16226                pending_update.borrow_mut().take().unwrap(),
16227                window,
16228                cx,
16229            )
16230        })
16231        .unwrap()
16232        .await
16233        .unwrap();
16234    assert_eq!(
16235        follower
16236            .update(cx, |follower, _, cx| follower.scroll_position(cx))
16237            .unwrap(),
16238        gpui::Point::new(1.5, 3.5)
16239    );
16240    assert!(*is_still_following.borrow());
16241    assert_eq!(*follower_edit_event_count.borrow(), 0);
16242
16243    // Update the selections and scroll position. The follower's scroll position is updated
16244    // via autoscroll, not via the leader's exact scroll position.
16245    _ = leader.update(cx, |leader, window, cx| {
16246        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16247            s.select_ranges([0..0])
16248        });
16249        leader.request_autoscroll(Autoscroll::newest(), cx);
16250        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16251    });
16252    follower
16253        .update(cx, |follower, window, cx| {
16254            follower.apply_update_proto(
16255                &project,
16256                pending_update.borrow_mut().take().unwrap(),
16257                window,
16258                cx,
16259            )
16260        })
16261        .unwrap()
16262        .await
16263        .unwrap();
16264    _ = follower.update(cx, |follower, _, cx| {
16265        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
16266        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
16267    });
16268    assert!(*is_still_following.borrow());
16269
16270    // Creating a pending selection that precedes another selection
16271    _ = leader.update(cx, |leader, window, cx| {
16272        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16273            s.select_ranges([1..1])
16274        });
16275        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
16276    });
16277    follower
16278        .update(cx, |follower, window, cx| {
16279            follower.apply_update_proto(
16280                &project,
16281                pending_update.borrow_mut().take().unwrap(),
16282                window,
16283                cx,
16284            )
16285        })
16286        .unwrap()
16287        .await
16288        .unwrap();
16289    _ = follower.update(cx, |follower, _, cx| {
16290        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
16291    });
16292    assert!(*is_still_following.borrow());
16293
16294    // Extend the pending selection so that it surrounds another selection
16295    _ = leader.update(cx, |leader, window, cx| {
16296        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
16297    });
16298    follower
16299        .update(cx, |follower, window, cx| {
16300            follower.apply_update_proto(
16301                &project,
16302                pending_update.borrow_mut().take().unwrap(),
16303                window,
16304                cx,
16305            )
16306        })
16307        .unwrap()
16308        .await
16309        .unwrap();
16310    _ = follower.update(cx, |follower, _, cx| {
16311        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
16312    });
16313
16314    // Scrolling locally breaks the follow
16315    _ = follower.update(cx, |follower, window, cx| {
16316        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
16317        follower.set_scroll_anchor(
16318            ScrollAnchor {
16319                anchor: top_anchor,
16320                offset: gpui::Point::new(0.0, 0.5),
16321            },
16322            window,
16323            cx,
16324        );
16325    });
16326    assert!(!(*is_still_following.borrow()));
16327}
16328
16329#[gpui::test]
16330async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
16331    init_test(cx, |_| {});
16332
16333    let fs = FakeFs::new(cx.executor());
16334    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16335    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16336    let pane = workspace
16337        .update(cx, |workspace, _, _| workspace.active_pane().clone())
16338        .unwrap();
16339
16340    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16341
16342    let leader = pane.update_in(cx, |_, window, cx| {
16343        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
16344        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
16345    });
16346
16347    // Start following the editor when it has no excerpts.
16348    let mut state_message =
16349        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16350    let workspace_entity = workspace.root(cx).unwrap();
16351    let follower_1 = cx
16352        .update_window(*workspace.deref(), |_, window, cx| {
16353            Editor::from_state_proto(
16354                workspace_entity,
16355                ViewId {
16356                    creator: CollaboratorId::PeerId(PeerId::default()),
16357                    id: 0,
16358                },
16359                &mut state_message,
16360                window,
16361                cx,
16362            )
16363        })
16364        .unwrap()
16365        .unwrap()
16366        .await
16367        .unwrap();
16368
16369    let update_message = Rc::new(RefCell::new(None));
16370    follower_1.update_in(cx, {
16371        let update = update_message.clone();
16372        |_, window, cx| {
16373            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
16374                leader.read(cx).add_event_to_update_proto(
16375                    event,
16376                    &mut update.borrow_mut(),
16377                    window,
16378                    cx,
16379                );
16380            })
16381            .detach();
16382        }
16383    });
16384
16385    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
16386        (
16387            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, false, cx),
16388            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, false, cx),
16389        )
16390    });
16391
16392    // Insert some excerpts.
16393    leader.update(cx, |leader, cx| {
16394        leader.buffer.update(cx, |multibuffer, cx| {
16395            multibuffer.set_excerpts_for_path(
16396                PathKey::namespaced(1, Arc::from(Path::new("b.txt"))),
16397                buffer_1.clone(),
16398                vec![
16399                    Point::row_range(0..3),
16400                    Point::row_range(1..6),
16401                    Point::row_range(12..15),
16402                ],
16403                0,
16404                cx,
16405            );
16406            multibuffer.set_excerpts_for_path(
16407                PathKey::namespaced(1, Arc::from(Path::new("a.txt"))),
16408                buffer_2.clone(),
16409                vec![Point::row_range(0..6), Point::row_range(8..12)],
16410                0,
16411                cx,
16412            );
16413        });
16414    });
16415
16416    // Apply the update of adding the excerpts.
16417    follower_1
16418        .update_in(cx, |follower, window, cx| {
16419            follower.apply_update_proto(
16420                &project,
16421                update_message.borrow().clone().unwrap(),
16422                window,
16423                cx,
16424            )
16425        })
16426        .await
16427        .unwrap();
16428    assert_eq!(
16429        follower_1.update(cx, |editor, cx| editor.text(cx)),
16430        leader.update(cx, |editor, cx| editor.text(cx))
16431    );
16432    update_message.borrow_mut().take();
16433
16434    // Start following separately after it already has excerpts.
16435    let mut state_message =
16436        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16437    let workspace_entity = workspace.root(cx).unwrap();
16438    let follower_2 = cx
16439        .update_window(*workspace.deref(), |_, window, cx| {
16440            Editor::from_state_proto(
16441                workspace_entity,
16442                ViewId {
16443                    creator: CollaboratorId::PeerId(PeerId::default()),
16444                    id: 0,
16445                },
16446                &mut state_message,
16447                window,
16448                cx,
16449            )
16450        })
16451        .unwrap()
16452        .unwrap()
16453        .await
16454        .unwrap();
16455    assert_eq!(
16456        follower_2.update(cx, |editor, cx| editor.text(cx)),
16457        leader.update(cx, |editor, cx| editor.text(cx))
16458    );
16459
16460    // Remove some excerpts.
16461    leader.update(cx, |leader, cx| {
16462        leader.buffer.update(cx, |multibuffer, cx| {
16463            let excerpt_ids = multibuffer.excerpt_ids();
16464            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
16465            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
16466        });
16467    });
16468
16469    // Apply the update of removing the excerpts.
16470    follower_1
16471        .update_in(cx, |follower, window, cx| {
16472            follower.apply_update_proto(
16473                &project,
16474                update_message.borrow().clone().unwrap(),
16475                window,
16476                cx,
16477            )
16478        })
16479        .await
16480        .unwrap();
16481    follower_2
16482        .update_in(cx, |follower, window, cx| {
16483            follower.apply_update_proto(
16484                &project,
16485                update_message.borrow().clone().unwrap(),
16486                window,
16487                cx,
16488            )
16489        })
16490        .await
16491        .unwrap();
16492    update_message.borrow_mut().take();
16493    assert_eq!(
16494        follower_1.update(cx, |editor, cx| editor.text(cx)),
16495        leader.update(cx, |editor, cx| editor.text(cx))
16496    );
16497}
16498
16499#[gpui::test]
16500async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16501    init_test(cx, |_| {});
16502
16503    let mut cx = EditorTestContext::new(cx).await;
16504    let lsp_store =
16505        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
16506
16507    cx.set_state(indoc! {"
16508        ˇfn func(abc def: i32) -> u32 {
16509        }
16510    "});
16511
16512    cx.update(|_, cx| {
16513        lsp_store.update(cx, |lsp_store, cx| {
16514            lsp_store
16515                .update_diagnostics(
16516                    LanguageServerId(0),
16517                    lsp::PublishDiagnosticsParams {
16518                        uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
16519                        version: None,
16520                        diagnostics: vec![
16521                            lsp::Diagnostic {
16522                                range: lsp::Range::new(
16523                                    lsp::Position::new(0, 11),
16524                                    lsp::Position::new(0, 12),
16525                                ),
16526                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16527                                ..Default::default()
16528                            },
16529                            lsp::Diagnostic {
16530                                range: lsp::Range::new(
16531                                    lsp::Position::new(0, 12),
16532                                    lsp::Position::new(0, 15),
16533                                ),
16534                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16535                                ..Default::default()
16536                            },
16537                            lsp::Diagnostic {
16538                                range: lsp::Range::new(
16539                                    lsp::Position::new(0, 25),
16540                                    lsp::Position::new(0, 28),
16541                                ),
16542                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16543                                ..Default::default()
16544                            },
16545                        ],
16546                    },
16547                    None,
16548                    DiagnosticSourceKind::Pushed,
16549                    &[],
16550                    cx,
16551                )
16552                .unwrap()
16553        });
16554    });
16555
16556    executor.run_until_parked();
16557
16558    cx.update_editor(|editor, window, cx| {
16559        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16560    });
16561
16562    cx.assert_editor_state(indoc! {"
16563        fn func(abc def: i32) -> ˇu32 {
16564        }
16565    "});
16566
16567    cx.update_editor(|editor, window, cx| {
16568        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16569    });
16570
16571    cx.assert_editor_state(indoc! {"
16572        fn func(abc ˇdef: i32) -> u32 {
16573        }
16574    "});
16575
16576    cx.update_editor(|editor, window, cx| {
16577        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16578    });
16579
16580    cx.assert_editor_state(indoc! {"
16581        fn func(abcˇ def: i32) -> u32 {
16582        }
16583    "});
16584
16585    cx.update_editor(|editor, window, cx| {
16586        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16587    });
16588
16589    cx.assert_editor_state(indoc! {"
16590        fn func(abc def: i32) -> ˇu32 {
16591        }
16592    "});
16593}
16594
16595#[gpui::test]
16596async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16597    init_test(cx, |_| {});
16598
16599    let mut cx = EditorTestContext::new(cx).await;
16600
16601    let diff_base = r#"
16602        use some::mod;
16603
16604        const A: u32 = 42;
16605
16606        fn main() {
16607            println!("hello");
16608
16609            println!("world");
16610        }
16611        "#
16612    .unindent();
16613
16614    // Edits are modified, removed, modified, added
16615    cx.set_state(
16616        &r#"
16617        use some::modified;
16618
16619        ˇ
16620        fn main() {
16621            println!("hello there");
16622
16623            println!("around the");
16624            println!("world");
16625        }
16626        "#
16627        .unindent(),
16628    );
16629
16630    cx.set_head_text(&diff_base);
16631    executor.run_until_parked();
16632
16633    cx.update_editor(|editor, window, cx| {
16634        //Wrap around the bottom of the buffer
16635        for _ in 0..3 {
16636            editor.go_to_next_hunk(&GoToHunk, window, cx);
16637        }
16638    });
16639
16640    cx.assert_editor_state(
16641        &r#"
16642        ˇuse some::modified;
16643
16644
16645        fn main() {
16646            println!("hello there");
16647
16648            println!("around the");
16649            println!("world");
16650        }
16651        "#
16652        .unindent(),
16653    );
16654
16655    cx.update_editor(|editor, window, cx| {
16656        //Wrap around the top of the buffer
16657        for _ in 0..2 {
16658            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16659        }
16660    });
16661
16662    cx.assert_editor_state(
16663        &r#"
16664        use some::modified;
16665
16666
16667        fn main() {
16668        ˇ    println!("hello there");
16669
16670            println!("around the");
16671            println!("world");
16672        }
16673        "#
16674        .unindent(),
16675    );
16676
16677    cx.update_editor(|editor, window, cx| {
16678        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16679    });
16680
16681    cx.assert_editor_state(
16682        &r#"
16683        use some::modified;
16684
16685        ˇ
16686        fn main() {
16687            println!("hello there");
16688
16689            println!("around the");
16690            println!("world");
16691        }
16692        "#
16693        .unindent(),
16694    );
16695
16696    cx.update_editor(|editor, window, cx| {
16697        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16698    });
16699
16700    cx.assert_editor_state(
16701        &r#"
16702        ˇuse some::modified;
16703
16704
16705        fn main() {
16706            println!("hello there");
16707
16708            println!("around the");
16709            println!("world");
16710        }
16711        "#
16712        .unindent(),
16713    );
16714
16715    cx.update_editor(|editor, window, cx| {
16716        for _ in 0..2 {
16717            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16718        }
16719    });
16720
16721    cx.assert_editor_state(
16722        &r#"
16723        use some::modified;
16724
16725
16726        fn main() {
16727        ˇ    println!("hello there");
16728
16729            println!("around the");
16730            println!("world");
16731        }
16732        "#
16733        .unindent(),
16734    );
16735
16736    cx.update_editor(|editor, window, cx| {
16737        editor.fold(&Fold, window, cx);
16738    });
16739
16740    cx.update_editor(|editor, window, cx| {
16741        editor.go_to_next_hunk(&GoToHunk, window, cx);
16742    });
16743
16744    cx.assert_editor_state(
16745        &r#"
16746        ˇuse some::modified;
16747
16748
16749        fn main() {
16750            println!("hello there");
16751
16752            println!("around the");
16753            println!("world");
16754        }
16755        "#
16756        .unindent(),
16757    );
16758}
16759
16760#[test]
16761fn test_split_words() {
16762    fn split(text: &str) -> Vec<&str> {
16763        split_words(text).collect()
16764    }
16765
16766    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
16767    assert_eq!(split("hello_world"), &["hello_", "world"]);
16768    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
16769    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
16770    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
16771    assert_eq!(split("helloworld"), &["helloworld"]);
16772
16773    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
16774}
16775
16776#[gpui::test]
16777async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
16778    init_test(cx, |_| {});
16779
16780    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
16781    let mut assert = |before, after| {
16782        let _state_context = cx.set_state(before);
16783        cx.run_until_parked();
16784        cx.update_editor(|editor, window, cx| {
16785            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
16786        });
16787        cx.run_until_parked();
16788        cx.assert_editor_state(after);
16789    };
16790
16791    // Outside bracket jumps to outside of matching bracket
16792    assert("console.logˇ(var);", "console.log(var)ˇ;");
16793    assert("console.log(var)ˇ;", "console.logˇ(var);");
16794
16795    // Inside bracket jumps to inside of matching bracket
16796    assert("console.log(ˇvar);", "console.log(varˇ);");
16797    assert("console.log(varˇ);", "console.log(ˇvar);");
16798
16799    // When outside a bracket and inside, favor jumping to the inside bracket
16800    assert(
16801        "console.log('foo', [1, 2, 3]ˇ);",
16802        "console.log(ˇ'foo', [1, 2, 3]);",
16803    );
16804    assert(
16805        "console.log(ˇ'foo', [1, 2, 3]);",
16806        "console.log('foo', [1, 2, 3]ˇ);",
16807    );
16808
16809    // Bias forward if two options are equally likely
16810    assert(
16811        "let result = curried_fun()ˇ();",
16812        "let result = curried_fun()()ˇ;",
16813    );
16814
16815    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
16816    assert(
16817        indoc! {"
16818            function test() {
16819                console.log('test')ˇ
16820            }"},
16821        indoc! {"
16822            function test() {
16823                console.logˇ('test')
16824            }"},
16825    );
16826}
16827
16828#[gpui::test]
16829async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
16830    init_test(cx, |_| {});
16831
16832    let fs = FakeFs::new(cx.executor());
16833    fs.insert_tree(
16834        path!("/a"),
16835        json!({
16836            "main.rs": "fn main() { let a = 5; }",
16837            "other.rs": "// Test file",
16838        }),
16839    )
16840    .await;
16841    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16842
16843    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
16844    language_registry.add(Arc::new(Language::new(
16845        LanguageConfig {
16846            name: "Rust".into(),
16847            matcher: LanguageMatcher {
16848                path_suffixes: vec!["rs".to_string()],
16849                ..Default::default()
16850            },
16851            brackets: BracketPairConfig {
16852                pairs: vec![BracketPair {
16853                    start: "{".to_string(),
16854                    end: "}".to_string(),
16855                    close: true,
16856                    surround: true,
16857                    newline: true,
16858                }],
16859                disabled_scopes_by_bracket_ix: Vec::new(),
16860            },
16861            ..Default::default()
16862        },
16863        Some(tree_sitter_rust::LANGUAGE.into()),
16864    )));
16865    let mut fake_servers = language_registry.register_fake_lsp(
16866        "Rust",
16867        FakeLspAdapter {
16868            capabilities: lsp::ServerCapabilities {
16869                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
16870                    first_trigger_character: "{".to_string(),
16871                    more_trigger_character: None,
16872                }),
16873                ..Default::default()
16874            },
16875            ..Default::default()
16876        },
16877    );
16878
16879    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16880
16881    let cx = &mut VisualTestContext::from_window(*workspace, cx);
16882
16883    let worktree_id = workspace
16884        .update(cx, |workspace, _, cx| {
16885            workspace.project().update(cx, |project, cx| {
16886                project.worktrees(cx).next().unwrap().read(cx).id()
16887            })
16888        })
16889        .unwrap();
16890
16891    let buffer = project
16892        .update(cx, |project, cx| {
16893            project.open_local_buffer(path!("/a/main.rs"), cx)
16894        })
16895        .await
16896        .unwrap();
16897    let editor_handle = workspace
16898        .update(cx, |workspace, window, cx| {
16899            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
16900        })
16901        .unwrap()
16902        .await
16903        .unwrap()
16904        .downcast::<Editor>()
16905        .unwrap();
16906
16907    cx.executor().start_waiting();
16908    let fake_server = fake_servers.next().await.unwrap();
16909
16910    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
16911        |params, _| async move {
16912            assert_eq!(
16913                params.text_document_position.text_document.uri,
16914                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
16915            );
16916            assert_eq!(
16917                params.text_document_position.position,
16918                lsp::Position::new(0, 21),
16919            );
16920
16921            Ok(Some(vec![lsp::TextEdit {
16922                new_text: "]".to_string(),
16923                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
16924            }]))
16925        },
16926    );
16927
16928    editor_handle.update_in(cx, |editor, window, cx| {
16929        window.focus(&editor.focus_handle(cx));
16930        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16931            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
16932        });
16933        editor.handle_input("{", window, cx);
16934    });
16935
16936    cx.executor().run_until_parked();
16937
16938    buffer.update(cx, |buffer, _| {
16939        assert_eq!(
16940            buffer.text(),
16941            "fn main() { let a = {5}; }",
16942            "No extra braces from on type formatting should appear in the buffer"
16943        )
16944    });
16945}
16946
16947#[gpui::test(iterations = 20, seeds(31))]
16948async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
16949    init_test(cx, |_| {});
16950
16951    let mut cx = EditorLspTestContext::new_rust(
16952        lsp::ServerCapabilities {
16953            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
16954                first_trigger_character: ".".to_string(),
16955                more_trigger_character: None,
16956            }),
16957            ..Default::default()
16958        },
16959        cx,
16960    )
16961    .await;
16962
16963    cx.update_buffer(|buffer, _| {
16964        // This causes autoindent to be async.
16965        buffer.set_sync_parse_timeout(Duration::ZERO)
16966    });
16967
16968    cx.set_state("fn c() {\n    d()ˇ\n}\n");
16969    cx.simulate_keystroke("\n");
16970    cx.run_until_parked();
16971
16972    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
16973    let mut request =
16974        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
16975            let buffer_cloned = buffer_cloned.clone();
16976            async move {
16977                buffer_cloned.update(&mut cx, |buffer, _| {
16978                    assert_eq!(
16979                        buffer.text(),
16980                        "fn c() {\n    d()\n        .\n}\n",
16981                        "OnTypeFormatting should triggered after autoindent applied"
16982                    )
16983                })?;
16984
16985                Ok(Some(vec![]))
16986            }
16987        });
16988
16989    cx.simulate_keystroke(".");
16990    cx.run_until_parked();
16991
16992    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
16993    assert!(request.next().await.is_some());
16994    request.close();
16995    assert!(request.next().await.is_none());
16996}
16997
16998#[gpui::test]
16999async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
17000    init_test(cx, |_| {});
17001
17002    let fs = FakeFs::new(cx.executor());
17003    fs.insert_tree(
17004        path!("/a"),
17005        json!({
17006            "main.rs": "fn main() { let a = 5; }",
17007            "other.rs": "// Test file",
17008        }),
17009    )
17010    .await;
17011
17012    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17013
17014    let server_restarts = Arc::new(AtomicUsize::new(0));
17015    let closure_restarts = Arc::clone(&server_restarts);
17016    let language_server_name = "test language server";
17017    let language_name: LanguageName = "Rust".into();
17018
17019    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17020    language_registry.add(Arc::new(Language::new(
17021        LanguageConfig {
17022            name: language_name.clone(),
17023            matcher: LanguageMatcher {
17024                path_suffixes: vec!["rs".to_string()],
17025                ..Default::default()
17026            },
17027            ..Default::default()
17028        },
17029        Some(tree_sitter_rust::LANGUAGE.into()),
17030    )));
17031    let mut fake_servers = language_registry.register_fake_lsp(
17032        "Rust",
17033        FakeLspAdapter {
17034            name: language_server_name,
17035            initialization_options: Some(json!({
17036                "testOptionValue": true
17037            })),
17038            initializer: Some(Box::new(move |fake_server| {
17039                let task_restarts = Arc::clone(&closure_restarts);
17040                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
17041                    task_restarts.fetch_add(1, atomic::Ordering::Release);
17042                    futures::future::ready(Ok(()))
17043                });
17044            })),
17045            ..Default::default()
17046        },
17047    );
17048
17049    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17050    let _buffer = project
17051        .update(cx, |project, cx| {
17052            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
17053        })
17054        .await
17055        .unwrap();
17056    let _fake_server = fake_servers.next().await.unwrap();
17057    update_test_language_settings(cx, |language_settings| {
17058        language_settings.languages.0.insert(
17059            language_name.clone().0,
17060            LanguageSettingsContent {
17061                tab_size: NonZeroU32::new(8),
17062                ..Default::default()
17063            },
17064        );
17065    });
17066    cx.executor().run_until_parked();
17067    assert_eq!(
17068        server_restarts.load(atomic::Ordering::Acquire),
17069        0,
17070        "Should not restart LSP server on an unrelated change"
17071    );
17072
17073    update_test_project_settings(cx, |project_settings| {
17074        project_settings.lsp.insert(
17075            "Some other server name".into(),
17076            LspSettings {
17077                binary: None,
17078                settings: None,
17079                initialization_options: Some(json!({
17080                    "some other init value": false
17081                })),
17082                enable_lsp_tasks: false,
17083                fetch: None,
17084            },
17085        );
17086    });
17087    cx.executor().run_until_parked();
17088    assert_eq!(
17089        server_restarts.load(atomic::Ordering::Acquire),
17090        0,
17091        "Should not restart LSP server on an unrelated LSP settings change"
17092    );
17093
17094    update_test_project_settings(cx, |project_settings| {
17095        project_settings.lsp.insert(
17096            language_server_name.into(),
17097            LspSettings {
17098                binary: None,
17099                settings: None,
17100                initialization_options: Some(json!({
17101                    "anotherInitValue": false
17102                })),
17103                enable_lsp_tasks: false,
17104                fetch: None,
17105            },
17106        );
17107    });
17108    cx.executor().run_until_parked();
17109    assert_eq!(
17110        server_restarts.load(atomic::Ordering::Acquire),
17111        1,
17112        "Should restart LSP server on a related LSP settings change"
17113    );
17114
17115    update_test_project_settings(cx, |project_settings| {
17116        project_settings.lsp.insert(
17117            language_server_name.into(),
17118            LspSettings {
17119                binary: None,
17120                settings: None,
17121                initialization_options: Some(json!({
17122                    "anotherInitValue": false
17123                })),
17124                enable_lsp_tasks: false,
17125                fetch: None,
17126            },
17127        );
17128    });
17129    cx.executor().run_until_parked();
17130    assert_eq!(
17131        server_restarts.load(atomic::Ordering::Acquire),
17132        1,
17133        "Should not restart LSP server on a related LSP settings change that is the same"
17134    );
17135
17136    update_test_project_settings(cx, |project_settings| {
17137        project_settings.lsp.insert(
17138            language_server_name.into(),
17139            LspSettings {
17140                binary: None,
17141                settings: None,
17142                initialization_options: None,
17143                enable_lsp_tasks: false,
17144                fetch: None,
17145            },
17146        );
17147    });
17148    cx.executor().run_until_parked();
17149    assert_eq!(
17150        server_restarts.load(atomic::Ordering::Acquire),
17151        2,
17152        "Should restart LSP server on another related LSP settings change"
17153    );
17154}
17155
17156#[gpui::test]
17157async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
17158    init_test(cx, |_| {});
17159
17160    let mut cx = EditorLspTestContext::new_rust(
17161        lsp::ServerCapabilities {
17162            completion_provider: Some(lsp::CompletionOptions {
17163                trigger_characters: Some(vec![".".to_string()]),
17164                resolve_provider: Some(true),
17165                ..Default::default()
17166            }),
17167            ..Default::default()
17168        },
17169        cx,
17170    )
17171    .await;
17172
17173    cx.set_state("fn main() { let a = 2ˇ; }");
17174    cx.simulate_keystroke(".");
17175    let completion_item = lsp::CompletionItem {
17176        label: "some".into(),
17177        kind: Some(lsp::CompletionItemKind::SNIPPET),
17178        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
17179        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
17180            kind: lsp::MarkupKind::Markdown,
17181            value: "```rust\nSome(2)\n```".to_string(),
17182        })),
17183        deprecated: Some(false),
17184        sort_text: Some("fffffff2".to_string()),
17185        filter_text: Some("some".to_string()),
17186        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
17187        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17188            range: lsp::Range {
17189                start: lsp::Position {
17190                    line: 0,
17191                    character: 22,
17192                },
17193                end: lsp::Position {
17194                    line: 0,
17195                    character: 22,
17196                },
17197            },
17198            new_text: "Some(2)".to_string(),
17199        })),
17200        additional_text_edits: Some(vec![lsp::TextEdit {
17201            range: lsp::Range {
17202                start: lsp::Position {
17203                    line: 0,
17204                    character: 20,
17205                },
17206                end: lsp::Position {
17207                    line: 0,
17208                    character: 22,
17209                },
17210            },
17211            new_text: "".to_string(),
17212        }]),
17213        ..Default::default()
17214    };
17215
17216    let closure_completion_item = completion_item.clone();
17217    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17218        let task_completion_item = closure_completion_item.clone();
17219        async move {
17220            Ok(Some(lsp::CompletionResponse::Array(vec![
17221                task_completion_item,
17222            ])))
17223        }
17224    });
17225
17226    request.next().await;
17227
17228    cx.condition(|editor, _| editor.context_menu_visible())
17229        .await;
17230    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
17231        editor
17232            .confirm_completion(&ConfirmCompletion::default(), window, cx)
17233            .unwrap()
17234    });
17235    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
17236
17237    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
17238        let task_completion_item = completion_item.clone();
17239        async move { Ok(task_completion_item) }
17240    })
17241    .next()
17242    .await
17243    .unwrap();
17244    apply_additional_edits.await.unwrap();
17245    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
17246}
17247
17248#[gpui::test]
17249async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
17250    init_test(cx, |_| {});
17251
17252    let mut cx = EditorLspTestContext::new_rust(
17253        lsp::ServerCapabilities {
17254            completion_provider: Some(lsp::CompletionOptions {
17255                trigger_characters: Some(vec![".".to_string()]),
17256                resolve_provider: Some(true),
17257                ..Default::default()
17258            }),
17259            ..Default::default()
17260        },
17261        cx,
17262    )
17263    .await;
17264
17265    cx.set_state("fn main() { let a = 2ˇ; }");
17266    cx.simulate_keystroke(".");
17267
17268    let item1 = lsp::CompletionItem {
17269        label: "method id()".to_string(),
17270        filter_text: Some("id".to_string()),
17271        detail: None,
17272        documentation: None,
17273        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17274            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17275            new_text: ".id".to_string(),
17276        })),
17277        ..lsp::CompletionItem::default()
17278    };
17279
17280    let item2 = lsp::CompletionItem {
17281        label: "other".to_string(),
17282        filter_text: Some("other".to_string()),
17283        detail: None,
17284        documentation: None,
17285        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17286            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17287            new_text: ".other".to_string(),
17288        })),
17289        ..lsp::CompletionItem::default()
17290    };
17291
17292    let item1 = item1.clone();
17293    cx.set_request_handler::<lsp::request::Completion, _, _>({
17294        let item1 = item1.clone();
17295        move |_, _, _| {
17296            let item1 = item1.clone();
17297            let item2 = item2.clone();
17298            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
17299        }
17300    })
17301    .next()
17302    .await;
17303
17304    cx.condition(|editor, _| editor.context_menu_visible())
17305        .await;
17306    cx.update_editor(|editor, _, _| {
17307        let context_menu = editor.context_menu.borrow_mut();
17308        let context_menu = context_menu
17309            .as_ref()
17310            .expect("Should have the context menu deployed");
17311        match context_menu {
17312            CodeContextMenu::Completions(completions_menu) => {
17313                let completions = completions_menu.completions.borrow_mut();
17314                assert_eq!(
17315                    completions
17316                        .iter()
17317                        .map(|completion| &completion.label.text)
17318                        .collect::<Vec<_>>(),
17319                    vec!["method id()", "other"]
17320                )
17321            }
17322            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17323        }
17324    });
17325
17326    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
17327        let item1 = item1.clone();
17328        move |_, item_to_resolve, _| {
17329            let item1 = item1.clone();
17330            async move {
17331                if item1 == item_to_resolve {
17332                    Ok(lsp::CompletionItem {
17333                        label: "method id()".to_string(),
17334                        filter_text: Some("id".to_string()),
17335                        detail: Some("Now resolved!".to_string()),
17336                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
17337                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17338                            range: lsp::Range::new(
17339                                lsp::Position::new(0, 22),
17340                                lsp::Position::new(0, 22),
17341                            ),
17342                            new_text: ".id".to_string(),
17343                        })),
17344                        ..lsp::CompletionItem::default()
17345                    })
17346                } else {
17347                    Ok(item_to_resolve)
17348                }
17349            }
17350        }
17351    })
17352    .next()
17353    .await
17354    .unwrap();
17355    cx.run_until_parked();
17356
17357    cx.update_editor(|editor, window, cx| {
17358        editor.context_menu_next(&Default::default(), window, cx);
17359    });
17360
17361    cx.update_editor(|editor, _, _| {
17362        let context_menu = editor.context_menu.borrow_mut();
17363        let context_menu = context_menu
17364            .as_ref()
17365            .expect("Should have the context menu deployed");
17366        match context_menu {
17367            CodeContextMenu::Completions(completions_menu) => {
17368                let completions = completions_menu.completions.borrow_mut();
17369                assert_eq!(
17370                    completions
17371                        .iter()
17372                        .map(|completion| &completion.label.text)
17373                        .collect::<Vec<_>>(),
17374                    vec!["method id() Now resolved!", "other"],
17375                    "Should update first completion label, but not second as the filter text did not match."
17376                );
17377            }
17378            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17379        }
17380    });
17381}
17382
17383#[gpui::test]
17384async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
17385    init_test(cx, |_| {});
17386    let mut cx = EditorLspTestContext::new_rust(
17387        lsp::ServerCapabilities {
17388            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
17389            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
17390            completion_provider: Some(lsp::CompletionOptions {
17391                resolve_provider: Some(true),
17392                ..Default::default()
17393            }),
17394            ..Default::default()
17395        },
17396        cx,
17397    )
17398    .await;
17399    cx.set_state(indoc! {"
17400        struct TestStruct {
17401            field: i32
17402        }
17403
17404        fn mainˇ() {
17405            let unused_var = 42;
17406            let test_struct = TestStruct { field: 42 };
17407        }
17408    "});
17409    let symbol_range = cx.lsp_range(indoc! {"
17410        struct TestStruct {
17411            field: i32
17412        }
17413
17414        «fn main»() {
17415            let unused_var = 42;
17416            let test_struct = TestStruct { field: 42 };
17417        }
17418    "});
17419    let mut hover_requests =
17420        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
17421            Ok(Some(lsp::Hover {
17422                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
17423                    kind: lsp::MarkupKind::Markdown,
17424                    value: "Function documentation".to_string(),
17425                }),
17426                range: Some(symbol_range),
17427            }))
17428        });
17429
17430    // Case 1: Test that code action menu hide hover popover
17431    cx.dispatch_action(Hover);
17432    hover_requests.next().await;
17433    cx.condition(|editor, _| editor.hover_state.visible()).await;
17434    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
17435        move |_, _, _| async move {
17436            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
17437                lsp::CodeAction {
17438                    title: "Remove unused variable".to_string(),
17439                    kind: Some(CodeActionKind::QUICKFIX),
17440                    edit: Some(lsp::WorkspaceEdit {
17441                        changes: Some(
17442                            [(
17443                                lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
17444                                vec![lsp::TextEdit {
17445                                    range: lsp::Range::new(
17446                                        lsp::Position::new(5, 4),
17447                                        lsp::Position::new(5, 27),
17448                                    ),
17449                                    new_text: "".to_string(),
17450                                }],
17451                            )]
17452                            .into_iter()
17453                            .collect(),
17454                        ),
17455                        ..Default::default()
17456                    }),
17457                    ..Default::default()
17458                },
17459            )]))
17460        },
17461    );
17462    cx.update_editor(|editor, window, cx| {
17463        editor.toggle_code_actions(
17464            &ToggleCodeActions {
17465                deployed_from: None,
17466                quick_launch: false,
17467            },
17468            window,
17469            cx,
17470        );
17471    });
17472    code_action_requests.next().await;
17473    cx.run_until_parked();
17474    cx.condition(|editor, _| editor.context_menu_visible())
17475        .await;
17476    cx.update_editor(|editor, _, _| {
17477        assert!(
17478            !editor.hover_state.visible(),
17479            "Hover popover should be hidden when code action menu is shown"
17480        );
17481        // Hide code actions
17482        editor.context_menu.take();
17483    });
17484
17485    // Case 2: Test that code completions hide hover popover
17486    cx.dispatch_action(Hover);
17487    hover_requests.next().await;
17488    cx.condition(|editor, _| editor.hover_state.visible()).await;
17489    let counter = Arc::new(AtomicUsize::new(0));
17490    let mut completion_requests =
17491        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17492            let counter = counter.clone();
17493            async move {
17494                counter.fetch_add(1, atomic::Ordering::Release);
17495                Ok(Some(lsp::CompletionResponse::Array(vec![
17496                    lsp::CompletionItem {
17497                        label: "main".into(),
17498                        kind: Some(lsp::CompletionItemKind::FUNCTION),
17499                        detail: Some("() -> ()".to_string()),
17500                        ..Default::default()
17501                    },
17502                    lsp::CompletionItem {
17503                        label: "TestStruct".into(),
17504                        kind: Some(lsp::CompletionItemKind::STRUCT),
17505                        detail: Some("struct TestStruct".to_string()),
17506                        ..Default::default()
17507                    },
17508                ])))
17509            }
17510        });
17511    cx.update_editor(|editor, window, cx| {
17512        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
17513    });
17514    completion_requests.next().await;
17515    cx.condition(|editor, _| editor.context_menu_visible())
17516        .await;
17517    cx.update_editor(|editor, _, _| {
17518        assert!(
17519            !editor.hover_state.visible(),
17520            "Hover popover should be hidden when completion menu is shown"
17521        );
17522    });
17523}
17524
17525#[gpui::test]
17526async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
17527    init_test(cx, |_| {});
17528
17529    let mut cx = EditorLspTestContext::new_rust(
17530        lsp::ServerCapabilities {
17531            completion_provider: Some(lsp::CompletionOptions {
17532                trigger_characters: Some(vec![".".to_string()]),
17533                resolve_provider: Some(true),
17534                ..Default::default()
17535            }),
17536            ..Default::default()
17537        },
17538        cx,
17539    )
17540    .await;
17541
17542    cx.set_state("fn main() { let a = 2ˇ; }");
17543    cx.simulate_keystroke(".");
17544
17545    let unresolved_item_1 = lsp::CompletionItem {
17546        label: "id".to_string(),
17547        filter_text: Some("id".to_string()),
17548        detail: None,
17549        documentation: None,
17550        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17551            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17552            new_text: ".id".to_string(),
17553        })),
17554        ..lsp::CompletionItem::default()
17555    };
17556    let resolved_item_1 = lsp::CompletionItem {
17557        additional_text_edits: Some(vec![lsp::TextEdit {
17558            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
17559            new_text: "!!".to_string(),
17560        }]),
17561        ..unresolved_item_1.clone()
17562    };
17563    let unresolved_item_2 = lsp::CompletionItem {
17564        label: "other".to_string(),
17565        filter_text: Some("other".to_string()),
17566        detail: None,
17567        documentation: None,
17568        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17569            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17570            new_text: ".other".to_string(),
17571        })),
17572        ..lsp::CompletionItem::default()
17573    };
17574    let resolved_item_2 = lsp::CompletionItem {
17575        additional_text_edits: Some(vec![lsp::TextEdit {
17576            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
17577            new_text: "??".to_string(),
17578        }]),
17579        ..unresolved_item_2.clone()
17580    };
17581
17582    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
17583    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
17584    cx.lsp
17585        .server
17586        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
17587            let unresolved_item_1 = unresolved_item_1.clone();
17588            let resolved_item_1 = resolved_item_1.clone();
17589            let unresolved_item_2 = unresolved_item_2.clone();
17590            let resolved_item_2 = resolved_item_2.clone();
17591            let resolve_requests_1 = resolve_requests_1.clone();
17592            let resolve_requests_2 = resolve_requests_2.clone();
17593            move |unresolved_request, _| {
17594                let unresolved_item_1 = unresolved_item_1.clone();
17595                let resolved_item_1 = resolved_item_1.clone();
17596                let unresolved_item_2 = unresolved_item_2.clone();
17597                let resolved_item_2 = resolved_item_2.clone();
17598                let resolve_requests_1 = resolve_requests_1.clone();
17599                let resolve_requests_2 = resolve_requests_2.clone();
17600                async move {
17601                    if unresolved_request == unresolved_item_1 {
17602                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
17603                        Ok(resolved_item_1.clone())
17604                    } else if unresolved_request == unresolved_item_2 {
17605                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
17606                        Ok(resolved_item_2.clone())
17607                    } else {
17608                        panic!("Unexpected completion item {unresolved_request:?}")
17609                    }
17610                }
17611            }
17612        })
17613        .detach();
17614
17615    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17616        let unresolved_item_1 = unresolved_item_1.clone();
17617        let unresolved_item_2 = unresolved_item_2.clone();
17618        async move {
17619            Ok(Some(lsp::CompletionResponse::Array(vec![
17620                unresolved_item_1,
17621                unresolved_item_2,
17622            ])))
17623        }
17624    })
17625    .next()
17626    .await;
17627
17628    cx.condition(|editor, _| editor.context_menu_visible())
17629        .await;
17630    cx.update_editor(|editor, _, _| {
17631        let context_menu = editor.context_menu.borrow_mut();
17632        let context_menu = context_menu
17633            .as_ref()
17634            .expect("Should have the context menu deployed");
17635        match context_menu {
17636            CodeContextMenu::Completions(completions_menu) => {
17637                let completions = completions_menu.completions.borrow_mut();
17638                assert_eq!(
17639                    completions
17640                        .iter()
17641                        .map(|completion| &completion.label.text)
17642                        .collect::<Vec<_>>(),
17643                    vec!["id", "other"]
17644                )
17645            }
17646            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17647        }
17648    });
17649    cx.run_until_parked();
17650
17651    cx.update_editor(|editor, window, cx| {
17652        editor.context_menu_next(&ContextMenuNext, window, cx);
17653    });
17654    cx.run_until_parked();
17655    cx.update_editor(|editor, window, cx| {
17656        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
17657    });
17658    cx.run_until_parked();
17659    cx.update_editor(|editor, window, cx| {
17660        editor.context_menu_next(&ContextMenuNext, window, cx);
17661    });
17662    cx.run_until_parked();
17663    cx.update_editor(|editor, window, cx| {
17664        editor
17665            .compose_completion(&ComposeCompletion::default(), window, cx)
17666            .expect("No task returned")
17667    })
17668    .await
17669    .expect("Completion failed");
17670    cx.run_until_parked();
17671
17672    cx.update_editor(|editor, _, cx| {
17673        assert_eq!(
17674            resolve_requests_1.load(atomic::Ordering::Acquire),
17675            1,
17676            "Should always resolve once despite multiple selections"
17677        );
17678        assert_eq!(
17679            resolve_requests_2.load(atomic::Ordering::Acquire),
17680            1,
17681            "Should always resolve once after multiple selections and applying the completion"
17682        );
17683        assert_eq!(
17684            editor.text(cx),
17685            "fn main() { let a = ??.other; }",
17686            "Should use resolved data when applying the completion"
17687        );
17688    });
17689}
17690
17691#[gpui::test]
17692async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
17693    init_test(cx, |_| {});
17694
17695    let item_0 = lsp::CompletionItem {
17696        label: "abs".into(),
17697        insert_text: Some("abs".into()),
17698        data: Some(json!({ "very": "special"})),
17699        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
17700        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
17701            lsp::InsertReplaceEdit {
17702                new_text: "abs".to_string(),
17703                insert: lsp::Range::default(),
17704                replace: lsp::Range::default(),
17705            },
17706        )),
17707        ..lsp::CompletionItem::default()
17708    };
17709    let items = iter::once(item_0.clone())
17710        .chain((11..51).map(|i| lsp::CompletionItem {
17711            label: format!("item_{}", i),
17712            insert_text: Some(format!("item_{}", i)),
17713            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
17714            ..lsp::CompletionItem::default()
17715        }))
17716        .collect::<Vec<_>>();
17717
17718    let default_commit_characters = vec!["?".to_string()];
17719    let default_data = json!({ "default": "data"});
17720    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
17721    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
17722    let default_edit_range = lsp::Range {
17723        start: lsp::Position {
17724            line: 0,
17725            character: 5,
17726        },
17727        end: lsp::Position {
17728            line: 0,
17729            character: 5,
17730        },
17731    };
17732
17733    let mut cx = EditorLspTestContext::new_rust(
17734        lsp::ServerCapabilities {
17735            completion_provider: Some(lsp::CompletionOptions {
17736                trigger_characters: Some(vec![".".to_string()]),
17737                resolve_provider: Some(true),
17738                ..Default::default()
17739            }),
17740            ..Default::default()
17741        },
17742        cx,
17743    )
17744    .await;
17745
17746    cx.set_state("fn main() { let a = 2ˇ; }");
17747    cx.simulate_keystroke(".");
17748
17749    let completion_data = default_data.clone();
17750    let completion_characters = default_commit_characters.clone();
17751    let completion_items = items.clone();
17752    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17753        let default_data = completion_data.clone();
17754        let default_commit_characters = completion_characters.clone();
17755        let items = completion_items.clone();
17756        async move {
17757            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
17758                items,
17759                item_defaults: Some(lsp::CompletionListItemDefaults {
17760                    data: Some(default_data.clone()),
17761                    commit_characters: Some(default_commit_characters.clone()),
17762                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
17763                        default_edit_range,
17764                    )),
17765                    insert_text_format: Some(default_insert_text_format),
17766                    insert_text_mode: Some(default_insert_text_mode),
17767                }),
17768                ..lsp::CompletionList::default()
17769            })))
17770        }
17771    })
17772    .next()
17773    .await;
17774
17775    let resolved_items = Arc::new(Mutex::new(Vec::new()));
17776    cx.lsp
17777        .server
17778        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
17779            let closure_resolved_items = resolved_items.clone();
17780            move |item_to_resolve, _| {
17781                let closure_resolved_items = closure_resolved_items.clone();
17782                async move {
17783                    closure_resolved_items.lock().push(item_to_resolve.clone());
17784                    Ok(item_to_resolve)
17785                }
17786            }
17787        })
17788        .detach();
17789
17790    cx.condition(|editor, _| editor.context_menu_visible())
17791        .await;
17792    cx.run_until_parked();
17793    cx.update_editor(|editor, _, _| {
17794        let menu = editor.context_menu.borrow_mut();
17795        match menu.as_ref().expect("should have the completions menu") {
17796            CodeContextMenu::Completions(completions_menu) => {
17797                assert_eq!(
17798                    completions_menu
17799                        .entries
17800                        .borrow()
17801                        .iter()
17802                        .map(|mat| mat.string.clone())
17803                        .collect::<Vec<String>>(),
17804                    items
17805                        .iter()
17806                        .map(|completion| completion.label.clone())
17807                        .collect::<Vec<String>>()
17808                );
17809            }
17810            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
17811        }
17812    });
17813    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
17814    // with 4 from the end.
17815    assert_eq!(
17816        *resolved_items.lock(),
17817        [&items[0..16], &items[items.len() - 4..items.len()]]
17818            .concat()
17819            .iter()
17820            .cloned()
17821            .map(|mut item| {
17822                if item.data.is_none() {
17823                    item.data = Some(default_data.clone());
17824                }
17825                item
17826            })
17827            .collect::<Vec<lsp::CompletionItem>>(),
17828        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
17829    );
17830    resolved_items.lock().clear();
17831
17832    cx.update_editor(|editor, window, cx| {
17833        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
17834    });
17835    cx.run_until_parked();
17836    // Completions that have already been resolved are skipped.
17837    assert_eq!(
17838        *resolved_items.lock(),
17839        items[items.len() - 17..items.len() - 4]
17840            .iter()
17841            .cloned()
17842            .map(|mut item| {
17843                if item.data.is_none() {
17844                    item.data = Some(default_data.clone());
17845                }
17846                item
17847            })
17848            .collect::<Vec<lsp::CompletionItem>>()
17849    );
17850    resolved_items.lock().clear();
17851}
17852
17853#[gpui::test]
17854async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
17855    init_test(cx, |_| {});
17856
17857    let mut cx = EditorLspTestContext::new(
17858        Language::new(
17859            LanguageConfig {
17860                matcher: LanguageMatcher {
17861                    path_suffixes: vec!["jsx".into()],
17862                    ..Default::default()
17863                },
17864                overrides: [(
17865                    "element".into(),
17866                    LanguageConfigOverride {
17867                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
17868                        ..Default::default()
17869                    },
17870                )]
17871                .into_iter()
17872                .collect(),
17873                ..Default::default()
17874            },
17875            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
17876        )
17877        .with_override_query("(jsx_self_closing_element) @element")
17878        .unwrap(),
17879        lsp::ServerCapabilities {
17880            completion_provider: Some(lsp::CompletionOptions {
17881                trigger_characters: Some(vec![":".to_string()]),
17882                ..Default::default()
17883            }),
17884            ..Default::default()
17885        },
17886        cx,
17887    )
17888    .await;
17889
17890    cx.lsp
17891        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
17892            Ok(Some(lsp::CompletionResponse::Array(vec![
17893                lsp::CompletionItem {
17894                    label: "bg-blue".into(),
17895                    ..Default::default()
17896                },
17897                lsp::CompletionItem {
17898                    label: "bg-red".into(),
17899                    ..Default::default()
17900                },
17901                lsp::CompletionItem {
17902                    label: "bg-yellow".into(),
17903                    ..Default::default()
17904                },
17905            ])))
17906        });
17907
17908    cx.set_state(r#"<p class="bgˇ" />"#);
17909
17910    // Trigger completion when typing a dash, because the dash is an extra
17911    // word character in the 'element' scope, which contains the cursor.
17912    cx.simulate_keystroke("-");
17913    cx.executor().run_until_parked();
17914    cx.update_editor(|editor, _, _| {
17915        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17916        {
17917            assert_eq!(
17918                completion_menu_entries(menu),
17919                &["bg-blue", "bg-red", "bg-yellow"]
17920            );
17921        } else {
17922            panic!("expected completion menu to be open");
17923        }
17924    });
17925
17926    cx.simulate_keystroke("l");
17927    cx.executor().run_until_parked();
17928    cx.update_editor(|editor, _, _| {
17929        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17930        {
17931            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
17932        } else {
17933            panic!("expected completion menu to be open");
17934        }
17935    });
17936
17937    // When filtering completions, consider the character after the '-' to
17938    // be the start of a subword.
17939    cx.set_state(r#"<p class="yelˇ" />"#);
17940    cx.simulate_keystroke("l");
17941    cx.executor().run_until_parked();
17942    cx.update_editor(|editor, _, _| {
17943        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17944        {
17945            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
17946        } else {
17947            panic!("expected completion menu to be open");
17948        }
17949    });
17950}
17951
17952fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
17953    let entries = menu.entries.borrow();
17954    entries.iter().map(|mat| mat.string.clone()).collect()
17955}
17956
17957#[gpui::test]
17958async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
17959    init_test(cx, |settings| {
17960        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
17961            Formatter::Prettier,
17962        )))
17963    });
17964
17965    let fs = FakeFs::new(cx.executor());
17966    fs.insert_file(path!("/file.ts"), Default::default()).await;
17967
17968    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
17969    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17970
17971    language_registry.add(Arc::new(Language::new(
17972        LanguageConfig {
17973            name: "TypeScript".into(),
17974            matcher: LanguageMatcher {
17975                path_suffixes: vec!["ts".to_string()],
17976                ..Default::default()
17977            },
17978            ..Default::default()
17979        },
17980        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
17981    )));
17982    update_test_language_settings(cx, |settings| {
17983        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
17984    });
17985
17986    let test_plugin = "test_plugin";
17987    let _ = language_registry.register_fake_lsp(
17988        "TypeScript",
17989        FakeLspAdapter {
17990            prettier_plugins: vec![test_plugin],
17991            ..Default::default()
17992        },
17993    );
17994
17995    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
17996    let buffer = project
17997        .update(cx, |project, cx| {
17998            project.open_local_buffer(path!("/file.ts"), cx)
17999        })
18000        .await
18001        .unwrap();
18002
18003    let buffer_text = "one\ntwo\nthree\n";
18004    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
18005    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
18006    editor.update_in(cx, |editor, window, cx| {
18007        editor.set_text(buffer_text, window, cx)
18008    });
18009
18010    editor
18011        .update_in(cx, |editor, window, cx| {
18012            editor.perform_format(
18013                project.clone(),
18014                FormatTrigger::Manual,
18015                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18016                window,
18017                cx,
18018            )
18019        })
18020        .unwrap()
18021        .await;
18022    assert_eq!(
18023        editor.update(cx, |editor, cx| editor.text(cx)),
18024        buffer_text.to_string() + prettier_format_suffix,
18025        "Test prettier formatting was not applied to the original buffer text",
18026    );
18027
18028    update_test_language_settings(cx, |settings| {
18029        settings.defaults.formatter = Some(SelectedFormatter::Auto)
18030    });
18031    let format = editor.update_in(cx, |editor, window, cx| {
18032        editor.perform_format(
18033            project.clone(),
18034            FormatTrigger::Manual,
18035            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18036            window,
18037            cx,
18038        )
18039    });
18040    format.await.unwrap();
18041    assert_eq!(
18042        editor.update(cx, |editor, cx| editor.text(cx)),
18043        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
18044        "Autoformatting (via test prettier) was not applied to the original buffer text",
18045    );
18046}
18047
18048#[gpui::test]
18049async fn test_addition_reverts(cx: &mut TestAppContext) {
18050    init_test(cx, |_| {});
18051    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18052    let base_text = indoc! {r#"
18053        struct Row;
18054        struct Row1;
18055        struct Row2;
18056
18057        struct Row4;
18058        struct Row5;
18059        struct Row6;
18060
18061        struct Row8;
18062        struct Row9;
18063        struct Row10;"#};
18064
18065    // When addition hunks are not adjacent to carets, no hunk revert is performed
18066    assert_hunk_revert(
18067        indoc! {r#"struct Row;
18068                   struct Row1;
18069                   struct Row1.1;
18070                   struct Row1.2;
18071                   struct Row2;ˇ
18072
18073                   struct Row4;
18074                   struct Row5;
18075                   struct Row6;
18076
18077                   struct Row8;
18078                   ˇstruct Row9;
18079                   struct Row9.1;
18080                   struct Row9.2;
18081                   struct Row9.3;
18082                   struct Row10;"#},
18083        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18084        indoc! {r#"struct Row;
18085                   struct Row1;
18086                   struct Row1.1;
18087                   struct Row1.2;
18088                   struct Row2;ˇ
18089
18090                   struct Row4;
18091                   struct Row5;
18092                   struct Row6;
18093
18094                   struct Row8;
18095                   ˇstruct Row9;
18096                   struct Row9.1;
18097                   struct Row9.2;
18098                   struct Row9.3;
18099                   struct Row10;"#},
18100        base_text,
18101        &mut cx,
18102    );
18103    // Same for selections
18104    assert_hunk_revert(
18105        indoc! {r#"struct Row;
18106                   struct Row1;
18107                   struct Row2;
18108                   struct Row2.1;
18109                   struct Row2.2;
18110                   «ˇ
18111                   struct Row4;
18112                   struct» Row5;
18113                   «struct Row6;
18114                   ˇ»
18115                   struct Row9.1;
18116                   struct Row9.2;
18117                   struct Row9.3;
18118                   struct Row8;
18119                   struct Row9;
18120                   struct Row10;"#},
18121        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18122        indoc! {r#"struct Row;
18123                   struct Row1;
18124                   struct Row2;
18125                   struct Row2.1;
18126                   struct Row2.2;
18127                   «ˇ
18128                   struct Row4;
18129                   struct» Row5;
18130                   «struct Row6;
18131                   ˇ»
18132                   struct Row9.1;
18133                   struct Row9.2;
18134                   struct Row9.3;
18135                   struct Row8;
18136                   struct Row9;
18137                   struct Row10;"#},
18138        base_text,
18139        &mut cx,
18140    );
18141
18142    // When carets and selections intersect the addition hunks, those are reverted.
18143    // Adjacent carets got merged.
18144    assert_hunk_revert(
18145        indoc! {r#"struct Row;
18146                   ˇ// something on the top
18147                   struct Row1;
18148                   struct Row2;
18149                   struct Roˇw3.1;
18150                   struct Row2.2;
18151                   struct Row2.3;ˇ
18152
18153                   struct Row4;
18154                   struct ˇRow5.1;
18155                   struct Row5.2;
18156                   struct «Rowˇ»5.3;
18157                   struct Row5;
18158                   struct Row6;
18159                   ˇ
18160                   struct Row9.1;
18161                   struct «Rowˇ»9.2;
18162                   struct «ˇRow»9.3;
18163                   struct Row8;
18164                   struct Row9;
18165                   «ˇ// something on bottom»
18166                   struct Row10;"#},
18167        vec![
18168            DiffHunkStatusKind::Added,
18169            DiffHunkStatusKind::Added,
18170            DiffHunkStatusKind::Added,
18171            DiffHunkStatusKind::Added,
18172            DiffHunkStatusKind::Added,
18173        ],
18174        indoc! {r#"struct Row;
18175                   ˇstruct Row1;
18176                   struct Row2;
18177                   ˇ
18178                   struct Row4;
18179                   ˇstruct Row5;
18180                   struct Row6;
18181                   ˇ
18182                   ˇstruct Row8;
18183                   struct Row9;
18184                   ˇstruct Row10;"#},
18185        base_text,
18186        &mut cx,
18187    );
18188}
18189
18190#[gpui::test]
18191async fn test_modification_reverts(cx: &mut TestAppContext) {
18192    init_test(cx, |_| {});
18193    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18194    let base_text = indoc! {r#"
18195        struct Row;
18196        struct Row1;
18197        struct Row2;
18198
18199        struct Row4;
18200        struct Row5;
18201        struct Row6;
18202
18203        struct Row8;
18204        struct Row9;
18205        struct Row10;"#};
18206
18207    // Modification hunks behave the same as the addition ones.
18208    assert_hunk_revert(
18209        indoc! {r#"struct Row;
18210                   struct Row1;
18211                   struct Row33;
18212                   ˇ
18213                   struct Row4;
18214                   struct Row5;
18215                   struct Row6;
18216                   ˇ
18217                   struct Row99;
18218                   struct Row9;
18219                   struct Row10;"#},
18220        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18221        indoc! {r#"struct Row;
18222                   struct Row1;
18223                   struct Row33;
18224                   ˇ
18225                   struct Row4;
18226                   struct Row5;
18227                   struct Row6;
18228                   ˇ
18229                   struct Row99;
18230                   struct Row9;
18231                   struct Row10;"#},
18232        base_text,
18233        &mut cx,
18234    );
18235    assert_hunk_revert(
18236        indoc! {r#"struct Row;
18237                   struct Row1;
18238                   struct Row33;
18239                   «ˇ
18240                   struct Row4;
18241                   struct» Row5;
18242                   «struct Row6;
18243                   ˇ»
18244                   struct Row99;
18245                   struct Row9;
18246                   struct Row10;"#},
18247        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18248        indoc! {r#"struct Row;
18249                   struct Row1;
18250                   struct Row33;
18251                   «ˇ
18252                   struct Row4;
18253                   struct» Row5;
18254                   «struct Row6;
18255                   ˇ»
18256                   struct Row99;
18257                   struct Row9;
18258                   struct Row10;"#},
18259        base_text,
18260        &mut cx,
18261    );
18262
18263    assert_hunk_revert(
18264        indoc! {r#"ˇstruct Row1.1;
18265                   struct Row1;
18266                   «ˇstr»uct Row22;
18267
18268                   struct ˇRow44;
18269                   struct Row5;
18270                   struct «Rˇ»ow66;ˇ
18271
18272                   «struˇ»ct Row88;
18273                   struct Row9;
18274                   struct Row1011;ˇ"#},
18275        vec![
18276            DiffHunkStatusKind::Modified,
18277            DiffHunkStatusKind::Modified,
18278            DiffHunkStatusKind::Modified,
18279            DiffHunkStatusKind::Modified,
18280            DiffHunkStatusKind::Modified,
18281            DiffHunkStatusKind::Modified,
18282        ],
18283        indoc! {r#"struct Row;
18284                   ˇstruct Row1;
18285                   struct Row2;
18286                   ˇ
18287                   struct Row4;
18288                   ˇstruct Row5;
18289                   struct Row6;
18290                   ˇ
18291                   struct Row8;
18292                   ˇstruct Row9;
18293                   struct Row10;ˇ"#},
18294        base_text,
18295        &mut cx,
18296    );
18297}
18298
18299#[gpui::test]
18300async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
18301    init_test(cx, |_| {});
18302    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18303    let base_text = indoc! {r#"
18304        one
18305
18306        two
18307        three
18308        "#};
18309
18310    cx.set_head_text(base_text);
18311    cx.set_state("\nˇ\n");
18312    cx.executor().run_until_parked();
18313    cx.update_editor(|editor, _window, cx| {
18314        editor.expand_selected_diff_hunks(cx);
18315    });
18316    cx.executor().run_until_parked();
18317    cx.update_editor(|editor, window, cx| {
18318        editor.backspace(&Default::default(), window, cx);
18319    });
18320    cx.run_until_parked();
18321    cx.assert_state_with_diff(
18322        indoc! {r#"
18323
18324        - two
18325        - threeˇ
18326        +
18327        "#}
18328        .to_string(),
18329    );
18330}
18331
18332#[gpui::test]
18333async fn test_deletion_reverts(cx: &mut TestAppContext) {
18334    init_test(cx, |_| {});
18335    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18336    let base_text = indoc! {r#"struct Row;
18337struct Row1;
18338struct Row2;
18339
18340struct Row4;
18341struct Row5;
18342struct Row6;
18343
18344struct Row8;
18345struct Row9;
18346struct Row10;"#};
18347
18348    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
18349    assert_hunk_revert(
18350        indoc! {r#"struct Row;
18351                   struct Row2;
18352
18353                   ˇstruct Row4;
18354                   struct Row5;
18355                   struct Row6;
18356                   ˇ
18357                   struct Row8;
18358                   struct Row10;"#},
18359        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18360        indoc! {r#"struct Row;
18361                   struct Row2;
18362
18363                   ˇstruct Row4;
18364                   struct Row5;
18365                   struct Row6;
18366                   ˇ
18367                   struct Row8;
18368                   struct Row10;"#},
18369        base_text,
18370        &mut cx,
18371    );
18372    assert_hunk_revert(
18373        indoc! {r#"struct Row;
18374                   struct Row2;
18375
18376                   «ˇstruct Row4;
18377                   struct» Row5;
18378                   «struct Row6;
18379                   ˇ»
18380                   struct Row8;
18381                   struct Row10;"#},
18382        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18383        indoc! {r#"struct Row;
18384                   struct Row2;
18385
18386                   «ˇstruct Row4;
18387                   struct» Row5;
18388                   «struct Row6;
18389                   ˇ»
18390                   struct Row8;
18391                   struct Row10;"#},
18392        base_text,
18393        &mut cx,
18394    );
18395
18396    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
18397    assert_hunk_revert(
18398        indoc! {r#"struct Row;
18399                   ˇstruct Row2;
18400
18401                   struct Row4;
18402                   struct Row5;
18403                   struct Row6;
18404
18405                   struct Row8;ˇ
18406                   struct Row10;"#},
18407        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18408        indoc! {r#"struct Row;
18409                   struct Row1;
18410                   ˇstruct Row2;
18411
18412                   struct Row4;
18413                   struct Row5;
18414                   struct Row6;
18415
18416                   struct Row8;ˇ
18417                   struct Row9;
18418                   struct Row10;"#},
18419        base_text,
18420        &mut cx,
18421    );
18422    assert_hunk_revert(
18423        indoc! {r#"struct Row;
18424                   struct Row2«ˇ;
18425                   struct Row4;
18426                   struct» Row5;
18427                   «struct Row6;
18428
18429                   struct Row8;ˇ»
18430                   struct Row10;"#},
18431        vec![
18432            DiffHunkStatusKind::Deleted,
18433            DiffHunkStatusKind::Deleted,
18434            DiffHunkStatusKind::Deleted,
18435        ],
18436        indoc! {r#"struct Row;
18437                   struct Row1;
18438                   struct Row2«ˇ;
18439
18440                   struct Row4;
18441                   struct» Row5;
18442                   «struct Row6;
18443
18444                   struct Row8;ˇ»
18445                   struct Row9;
18446                   struct Row10;"#},
18447        base_text,
18448        &mut cx,
18449    );
18450}
18451
18452#[gpui::test]
18453async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
18454    init_test(cx, |_| {});
18455
18456    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
18457    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
18458    let base_text_3 =
18459        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
18460
18461    let text_1 = edit_first_char_of_every_line(base_text_1);
18462    let text_2 = edit_first_char_of_every_line(base_text_2);
18463    let text_3 = edit_first_char_of_every_line(base_text_3);
18464
18465    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
18466    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
18467    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
18468
18469    let multibuffer = cx.new(|cx| {
18470        let mut multibuffer = MultiBuffer::new(ReadWrite);
18471        multibuffer.push_excerpts(
18472            buffer_1.clone(),
18473            [
18474                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18475                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18476                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18477            ],
18478            cx,
18479        );
18480        multibuffer.push_excerpts(
18481            buffer_2.clone(),
18482            [
18483                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18484                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18485                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18486            ],
18487            cx,
18488        );
18489        multibuffer.push_excerpts(
18490            buffer_3.clone(),
18491            [
18492                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18493                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18494                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18495            ],
18496            cx,
18497        );
18498        multibuffer
18499    });
18500
18501    let fs = FakeFs::new(cx.executor());
18502    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
18503    let (editor, cx) = cx
18504        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
18505    editor.update_in(cx, |editor, _window, cx| {
18506        for (buffer, diff_base) in [
18507            (buffer_1.clone(), base_text_1),
18508            (buffer_2.clone(), base_text_2),
18509            (buffer_3.clone(), base_text_3),
18510        ] {
18511            let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
18512            editor
18513                .buffer
18514                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
18515        }
18516    });
18517    cx.executor().run_until_parked();
18518
18519    editor.update_in(cx, |editor, window, cx| {
18520        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}");
18521        editor.select_all(&SelectAll, window, cx);
18522        editor.git_restore(&Default::default(), window, cx);
18523    });
18524    cx.executor().run_until_parked();
18525
18526    // When all ranges are selected, all buffer hunks are reverted.
18527    editor.update(cx, |editor, cx| {
18528        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");
18529    });
18530    buffer_1.update(cx, |buffer, _| {
18531        assert_eq!(buffer.text(), base_text_1);
18532    });
18533    buffer_2.update(cx, |buffer, _| {
18534        assert_eq!(buffer.text(), base_text_2);
18535    });
18536    buffer_3.update(cx, |buffer, _| {
18537        assert_eq!(buffer.text(), base_text_3);
18538    });
18539
18540    editor.update_in(cx, |editor, window, cx| {
18541        editor.undo(&Default::default(), window, cx);
18542    });
18543
18544    editor.update_in(cx, |editor, window, cx| {
18545        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18546            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
18547        });
18548        editor.git_restore(&Default::default(), window, cx);
18549    });
18550
18551    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
18552    // but not affect buffer_2 and its related excerpts.
18553    editor.update(cx, |editor, cx| {
18554        assert_eq!(
18555            editor.text(cx),
18556            "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}"
18557        );
18558    });
18559    buffer_1.update(cx, |buffer, _| {
18560        assert_eq!(buffer.text(), base_text_1);
18561    });
18562    buffer_2.update(cx, |buffer, _| {
18563        assert_eq!(
18564            buffer.text(),
18565            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
18566        );
18567    });
18568    buffer_3.update(cx, |buffer, _| {
18569        assert_eq!(
18570            buffer.text(),
18571            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
18572        );
18573    });
18574
18575    fn edit_first_char_of_every_line(text: &str) -> String {
18576        text.split('\n')
18577            .map(|line| format!("X{}", &line[1..]))
18578            .collect::<Vec<_>>()
18579            .join("\n")
18580    }
18581}
18582
18583#[gpui::test]
18584async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
18585    init_test(cx, |_| {});
18586
18587    let cols = 4;
18588    let rows = 10;
18589    let sample_text_1 = sample_text(rows, cols, 'a');
18590    assert_eq!(
18591        sample_text_1,
18592        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
18593    );
18594    let sample_text_2 = sample_text(rows, cols, 'l');
18595    assert_eq!(
18596        sample_text_2,
18597        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
18598    );
18599    let sample_text_3 = sample_text(rows, cols, 'v');
18600    assert_eq!(
18601        sample_text_3,
18602        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
18603    );
18604
18605    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
18606    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
18607    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
18608
18609    let multi_buffer = cx.new(|cx| {
18610        let mut multibuffer = MultiBuffer::new(ReadWrite);
18611        multibuffer.push_excerpts(
18612            buffer_1.clone(),
18613            [
18614                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18615                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18616                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18617            ],
18618            cx,
18619        );
18620        multibuffer.push_excerpts(
18621            buffer_2.clone(),
18622            [
18623                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18624                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18625                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18626            ],
18627            cx,
18628        );
18629        multibuffer.push_excerpts(
18630            buffer_3.clone(),
18631            [
18632                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18633                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18634                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18635            ],
18636            cx,
18637        );
18638        multibuffer
18639    });
18640
18641    let fs = FakeFs::new(cx.executor());
18642    fs.insert_tree(
18643        "/a",
18644        json!({
18645            "main.rs": sample_text_1,
18646            "other.rs": sample_text_2,
18647            "lib.rs": sample_text_3,
18648        }),
18649    )
18650    .await;
18651    let project = Project::test(fs, ["/a".as_ref()], cx).await;
18652    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18653    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18654    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
18655        Editor::new(
18656            EditorMode::full(),
18657            multi_buffer,
18658            Some(project.clone()),
18659            window,
18660            cx,
18661        )
18662    });
18663    let multibuffer_item_id = workspace
18664        .update(cx, |workspace, window, cx| {
18665            assert!(
18666                workspace.active_item(cx).is_none(),
18667                "active item should be None before the first item is added"
18668            );
18669            workspace.add_item_to_active_pane(
18670                Box::new(multi_buffer_editor.clone()),
18671                None,
18672                true,
18673                window,
18674                cx,
18675            );
18676            let active_item = workspace
18677                .active_item(cx)
18678                .expect("should have an active item after adding the multi buffer");
18679            assert!(
18680                !active_item.is_singleton(cx),
18681                "A multi buffer was expected to active after adding"
18682            );
18683            active_item.item_id()
18684        })
18685        .unwrap();
18686    cx.executor().run_until_parked();
18687
18688    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18689        editor.change_selections(
18690            SelectionEffects::scroll(Autoscroll::Next),
18691            window,
18692            cx,
18693            |s| s.select_ranges(Some(1..2)),
18694        );
18695        editor.open_excerpts(&OpenExcerpts, window, cx);
18696    });
18697    cx.executor().run_until_parked();
18698    let first_item_id = workspace
18699        .update(cx, |workspace, window, cx| {
18700            let active_item = workspace
18701                .active_item(cx)
18702                .expect("should have an active item after navigating into the 1st buffer");
18703            let first_item_id = active_item.item_id();
18704            assert_ne!(
18705                first_item_id, multibuffer_item_id,
18706                "Should navigate into the 1st buffer and activate it"
18707            );
18708            assert!(
18709                active_item.is_singleton(cx),
18710                "New active item should be a singleton buffer"
18711            );
18712            assert_eq!(
18713                active_item
18714                    .act_as::<Editor>(cx)
18715                    .expect("should have navigated into an editor for the 1st buffer")
18716                    .read(cx)
18717                    .text(cx),
18718                sample_text_1
18719            );
18720
18721            workspace
18722                .go_back(workspace.active_pane().downgrade(), window, cx)
18723                .detach_and_log_err(cx);
18724
18725            first_item_id
18726        })
18727        .unwrap();
18728    cx.executor().run_until_parked();
18729    workspace
18730        .update(cx, |workspace, _, cx| {
18731            let active_item = workspace
18732                .active_item(cx)
18733                .expect("should have an active item after navigating back");
18734            assert_eq!(
18735                active_item.item_id(),
18736                multibuffer_item_id,
18737                "Should navigate back to the multi buffer"
18738            );
18739            assert!(!active_item.is_singleton(cx));
18740        })
18741        .unwrap();
18742
18743    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18744        editor.change_selections(
18745            SelectionEffects::scroll(Autoscroll::Next),
18746            window,
18747            cx,
18748            |s| s.select_ranges(Some(39..40)),
18749        );
18750        editor.open_excerpts(&OpenExcerpts, window, cx);
18751    });
18752    cx.executor().run_until_parked();
18753    let second_item_id = workspace
18754        .update(cx, |workspace, window, cx| {
18755            let active_item = workspace
18756                .active_item(cx)
18757                .expect("should have an active item after navigating into the 2nd buffer");
18758            let second_item_id = active_item.item_id();
18759            assert_ne!(
18760                second_item_id, multibuffer_item_id,
18761                "Should navigate away from the multibuffer"
18762            );
18763            assert_ne!(
18764                second_item_id, first_item_id,
18765                "Should navigate into the 2nd buffer and activate it"
18766            );
18767            assert!(
18768                active_item.is_singleton(cx),
18769                "New active item should be a singleton buffer"
18770            );
18771            assert_eq!(
18772                active_item
18773                    .act_as::<Editor>(cx)
18774                    .expect("should have navigated into an editor")
18775                    .read(cx)
18776                    .text(cx),
18777                sample_text_2
18778            );
18779
18780            workspace
18781                .go_back(workspace.active_pane().downgrade(), window, cx)
18782                .detach_and_log_err(cx);
18783
18784            second_item_id
18785        })
18786        .unwrap();
18787    cx.executor().run_until_parked();
18788    workspace
18789        .update(cx, |workspace, _, cx| {
18790            let active_item = workspace
18791                .active_item(cx)
18792                .expect("should have an active item after navigating back from the 2nd buffer");
18793            assert_eq!(
18794                active_item.item_id(),
18795                multibuffer_item_id,
18796                "Should navigate back from the 2nd buffer to the multi buffer"
18797            );
18798            assert!(!active_item.is_singleton(cx));
18799        })
18800        .unwrap();
18801
18802    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18803        editor.change_selections(
18804            SelectionEffects::scroll(Autoscroll::Next),
18805            window,
18806            cx,
18807            |s| s.select_ranges(Some(70..70)),
18808        );
18809        editor.open_excerpts(&OpenExcerpts, window, cx);
18810    });
18811    cx.executor().run_until_parked();
18812    workspace
18813        .update(cx, |workspace, window, cx| {
18814            let active_item = workspace
18815                .active_item(cx)
18816                .expect("should have an active item after navigating into the 3rd buffer");
18817            let third_item_id = active_item.item_id();
18818            assert_ne!(
18819                third_item_id, multibuffer_item_id,
18820                "Should navigate into the 3rd buffer and activate it"
18821            );
18822            assert_ne!(third_item_id, first_item_id);
18823            assert_ne!(third_item_id, second_item_id);
18824            assert!(
18825                active_item.is_singleton(cx),
18826                "New active item should be a singleton buffer"
18827            );
18828            assert_eq!(
18829                active_item
18830                    .act_as::<Editor>(cx)
18831                    .expect("should have navigated into an editor")
18832                    .read(cx)
18833                    .text(cx),
18834                sample_text_3
18835            );
18836
18837            workspace
18838                .go_back(workspace.active_pane().downgrade(), window, cx)
18839                .detach_and_log_err(cx);
18840        })
18841        .unwrap();
18842    cx.executor().run_until_parked();
18843    workspace
18844        .update(cx, |workspace, _, cx| {
18845            let active_item = workspace
18846                .active_item(cx)
18847                .expect("should have an active item after navigating back from the 3rd buffer");
18848            assert_eq!(
18849                active_item.item_id(),
18850                multibuffer_item_id,
18851                "Should navigate back from the 3rd buffer to the multi buffer"
18852            );
18853            assert!(!active_item.is_singleton(cx));
18854        })
18855        .unwrap();
18856}
18857
18858#[gpui::test]
18859async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
18860    init_test(cx, |_| {});
18861
18862    let mut cx = EditorTestContext::new(cx).await;
18863
18864    let diff_base = r#"
18865        use some::mod;
18866
18867        const A: u32 = 42;
18868
18869        fn main() {
18870            println!("hello");
18871
18872            println!("world");
18873        }
18874        "#
18875    .unindent();
18876
18877    cx.set_state(
18878        &r#"
18879        use some::modified;
18880
18881        ˇ
18882        fn main() {
18883            println!("hello there");
18884
18885            println!("around the");
18886            println!("world");
18887        }
18888        "#
18889        .unindent(),
18890    );
18891
18892    cx.set_head_text(&diff_base);
18893    executor.run_until_parked();
18894
18895    cx.update_editor(|editor, window, cx| {
18896        editor.go_to_next_hunk(&GoToHunk, window, cx);
18897        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
18898    });
18899    executor.run_until_parked();
18900    cx.assert_state_with_diff(
18901        r#"
18902          use some::modified;
18903
18904
18905          fn main() {
18906        -     println!("hello");
18907        + ˇ    println!("hello there");
18908
18909              println!("around the");
18910              println!("world");
18911          }
18912        "#
18913        .unindent(),
18914    );
18915
18916    cx.update_editor(|editor, window, cx| {
18917        for _ in 0..2 {
18918            editor.go_to_next_hunk(&GoToHunk, window, cx);
18919            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
18920        }
18921    });
18922    executor.run_until_parked();
18923    cx.assert_state_with_diff(
18924        r#"
18925        - use some::mod;
18926        + ˇuse some::modified;
18927
18928
18929          fn main() {
18930        -     println!("hello");
18931        +     println!("hello there");
18932
18933        +     println!("around the");
18934              println!("world");
18935          }
18936        "#
18937        .unindent(),
18938    );
18939
18940    cx.update_editor(|editor, window, cx| {
18941        editor.go_to_next_hunk(&GoToHunk, window, cx);
18942        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
18943    });
18944    executor.run_until_parked();
18945    cx.assert_state_with_diff(
18946        r#"
18947        - use some::mod;
18948        + use some::modified;
18949
18950        - const A: u32 = 42;
18951          ˇ
18952          fn main() {
18953        -     println!("hello");
18954        +     println!("hello there");
18955
18956        +     println!("around the");
18957              println!("world");
18958          }
18959        "#
18960        .unindent(),
18961    );
18962
18963    cx.update_editor(|editor, window, cx| {
18964        editor.cancel(&Cancel, window, cx);
18965    });
18966
18967    cx.assert_state_with_diff(
18968        r#"
18969          use some::modified;
18970
18971          ˇ
18972          fn main() {
18973              println!("hello there");
18974
18975              println!("around the");
18976              println!("world");
18977          }
18978        "#
18979        .unindent(),
18980    );
18981}
18982
18983#[gpui::test]
18984async fn test_diff_base_change_with_expanded_diff_hunks(
18985    executor: BackgroundExecutor,
18986    cx: &mut TestAppContext,
18987) {
18988    init_test(cx, |_| {});
18989
18990    let mut cx = EditorTestContext::new(cx).await;
18991
18992    let diff_base = r#"
18993        use some::mod1;
18994        use some::mod2;
18995
18996        const A: u32 = 42;
18997        const B: u32 = 42;
18998        const C: u32 = 42;
18999
19000        fn main() {
19001            println!("hello");
19002
19003            println!("world");
19004        }
19005        "#
19006    .unindent();
19007
19008    cx.set_state(
19009        &r#"
19010        use some::mod2;
19011
19012        const A: u32 = 42;
19013        const C: u32 = 42;
19014
19015        fn main(ˇ) {
19016            //println!("hello");
19017
19018            println!("world");
19019            //
19020            //
19021        }
19022        "#
19023        .unindent(),
19024    );
19025
19026    cx.set_head_text(&diff_base);
19027    executor.run_until_parked();
19028
19029    cx.update_editor(|editor, window, cx| {
19030        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19031    });
19032    executor.run_until_parked();
19033    cx.assert_state_with_diff(
19034        r#"
19035        - use some::mod1;
19036          use some::mod2;
19037
19038          const A: u32 = 42;
19039        - const B: u32 = 42;
19040          const C: u32 = 42;
19041
19042          fn main(ˇ) {
19043        -     println!("hello");
19044        +     //println!("hello");
19045
19046              println!("world");
19047        +     //
19048        +     //
19049          }
19050        "#
19051        .unindent(),
19052    );
19053
19054    cx.set_head_text("new diff base!");
19055    executor.run_until_parked();
19056    cx.assert_state_with_diff(
19057        r#"
19058        - new diff base!
19059        + use some::mod2;
19060        +
19061        + const A: u32 = 42;
19062        + const C: u32 = 42;
19063        +
19064        + fn main(ˇ) {
19065        +     //println!("hello");
19066        +
19067        +     println!("world");
19068        +     //
19069        +     //
19070        + }
19071        "#
19072        .unindent(),
19073    );
19074}
19075
19076#[gpui::test]
19077async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
19078    init_test(cx, |_| {});
19079
19080    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19081    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19082    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19083    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19084    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
19085    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
19086
19087    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
19088    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
19089    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
19090
19091    let multi_buffer = cx.new(|cx| {
19092        let mut multibuffer = MultiBuffer::new(ReadWrite);
19093        multibuffer.push_excerpts(
19094            buffer_1.clone(),
19095            [
19096                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19097                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19098                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19099            ],
19100            cx,
19101        );
19102        multibuffer.push_excerpts(
19103            buffer_2.clone(),
19104            [
19105                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19106                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19107                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19108            ],
19109            cx,
19110        );
19111        multibuffer.push_excerpts(
19112            buffer_3.clone(),
19113            [
19114                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19115                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19116                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19117            ],
19118            cx,
19119        );
19120        multibuffer
19121    });
19122
19123    let editor =
19124        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19125    editor
19126        .update(cx, |editor, _window, cx| {
19127            for (buffer, diff_base) in [
19128                (buffer_1.clone(), file_1_old),
19129                (buffer_2.clone(), file_2_old),
19130                (buffer_3.clone(), file_3_old),
19131            ] {
19132                let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
19133                editor
19134                    .buffer
19135                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
19136            }
19137        })
19138        .unwrap();
19139
19140    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19141    cx.run_until_parked();
19142
19143    cx.assert_editor_state(
19144        &"
19145            ˇaaa
19146            ccc
19147            ddd
19148
19149            ggg
19150            hhh
19151
19152
19153            lll
19154            mmm
19155            NNN
19156
19157            qqq
19158            rrr
19159
19160            uuu
19161            111
19162            222
19163            333
19164
19165            666
19166            777
19167
19168            000
19169            !!!"
19170        .unindent(),
19171    );
19172
19173    cx.update_editor(|editor, window, cx| {
19174        editor.select_all(&SelectAll, window, cx);
19175        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19176    });
19177    cx.executor().run_until_parked();
19178
19179    cx.assert_state_with_diff(
19180        "
19181            «aaa
19182          - bbb
19183            ccc
19184            ddd
19185
19186            ggg
19187            hhh
19188
19189
19190            lll
19191            mmm
19192          - nnn
19193          + NNN
19194
19195            qqq
19196            rrr
19197
19198            uuu
19199            111
19200            222
19201            333
19202
19203          + 666
19204            777
19205
19206            000
19207            !!!ˇ»"
19208            .unindent(),
19209    );
19210}
19211
19212#[gpui::test]
19213async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
19214    init_test(cx, |_| {});
19215
19216    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
19217    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
19218
19219    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
19220    let multi_buffer = cx.new(|cx| {
19221        let mut multibuffer = MultiBuffer::new(ReadWrite);
19222        multibuffer.push_excerpts(
19223            buffer.clone(),
19224            [
19225                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
19226                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
19227                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
19228            ],
19229            cx,
19230        );
19231        multibuffer
19232    });
19233
19234    let editor =
19235        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19236    editor
19237        .update(cx, |editor, _window, cx| {
19238            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
19239            editor
19240                .buffer
19241                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
19242        })
19243        .unwrap();
19244
19245    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19246    cx.run_until_parked();
19247
19248    cx.update_editor(|editor, window, cx| {
19249        editor.expand_all_diff_hunks(&Default::default(), window, cx)
19250    });
19251    cx.executor().run_until_parked();
19252
19253    // When the start of a hunk coincides with the start of its excerpt,
19254    // the hunk is expanded. When the start of a hunk is earlier than
19255    // the start of its excerpt, the hunk is not expanded.
19256    cx.assert_state_with_diff(
19257        "
19258            ˇaaa
19259          - bbb
19260          + BBB
19261
19262          - ddd
19263          - eee
19264          + DDD
19265          + EEE
19266            fff
19267
19268            iii
19269        "
19270        .unindent(),
19271    );
19272}
19273
19274#[gpui::test]
19275async fn test_edits_around_expanded_insertion_hunks(
19276    executor: BackgroundExecutor,
19277    cx: &mut TestAppContext,
19278) {
19279    init_test(cx, |_| {});
19280
19281    let mut cx = EditorTestContext::new(cx).await;
19282
19283    let diff_base = r#"
19284        use some::mod1;
19285        use some::mod2;
19286
19287        const A: u32 = 42;
19288
19289        fn main() {
19290            println!("hello");
19291
19292            println!("world");
19293        }
19294        "#
19295    .unindent();
19296    executor.run_until_parked();
19297    cx.set_state(
19298        &r#"
19299        use some::mod1;
19300        use some::mod2;
19301
19302        const A: u32 = 42;
19303        const B: u32 = 42;
19304        const C: u32 = 42;
19305        ˇ
19306
19307        fn main() {
19308            println!("hello");
19309
19310            println!("world");
19311        }
19312        "#
19313        .unindent(),
19314    );
19315
19316    cx.set_head_text(&diff_base);
19317    executor.run_until_parked();
19318
19319    cx.update_editor(|editor, window, cx| {
19320        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19321    });
19322    executor.run_until_parked();
19323
19324    cx.assert_state_with_diff(
19325        r#"
19326        use some::mod1;
19327        use some::mod2;
19328
19329        const A: u32 = 42;
19330      + const B: u32 = 42;
19331      + const C: u32 = 42;
19332      + ˇ
19333
19334        fn main() {
19335            println!("hello");
19336
19337            println!("world");
19338        }
19339      "#
19340        .unindent(),
19341    );
19342
19343    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
19344    executor.run_until_parked();
19345
19346    cx.assert_state_with_diff(
19347        r#"
19348        use some::mod1;
19349        use some::mod2;
19350
19351        const A: u32 = 42;
19352      + const B: u32 = 42;
19353      + const C: u32 = 42;
19354      + const D: u32 = 42;
19355      + ˇ
19356
19357        fn main() {
19358            println!("hello");
19359
19360            println!("world");
19361        }
19362      "#
19363        .unindent(),
19364    );
19365
19366    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
19367    executor.run_until_parked();
19368
19369    cx.assert_state_with_diff(
19370        r#"
19371        use some::mod1;
19372        use some::mod2;
19373
19374        const A: u32 = 42;
19375      + const B: u32 = 42;
19376      + const C: u32 = 42;
19377      + const D: u32 = 42;
19378      + const E: u32 = 42;
19379      + ˇ
19380
19381        fn main() {
19382            println!("hello");
19383
19384            println!("world");
19385        }
19386      "#
19387        .unindent(),
19388    );
19389
19390    cx.update_editor(|editor, window, cx| {
19391        editor.delete_line(&DeleteLine, window, cx);
19392    });
19393    executor.run_until_parked();
19394
19395    cx.assert_state_with_diff(
19396        r#"
19397        use some::mod1;
19398        use some::mod2;
19399
19400        const A: u32 = 42;
19401      + const B: u32 = 42;
19402      + const C: u32 = 42;
19403      + const D: u32 = 42;
19404      + const E: u32 = 42;
19405        ˇ
19406        fn main() {
19407            println!("hello");
19408
19409            println!("world");
19410        }
19411      "#
19412        .unindent(),
19413    );
19414
19415    cx.update_editor(|editor, window, cx| {
19416        editor.move_up(&MoveUp, window, cx);
19417        editor.delete_line(&DeleteLine, window, cx);
19418        editor.move_up(&MoveUp, window, cx);
19419        editor.delete_line(&DeleteLine, window, cx);
19420        editor.move_up(&MoveUp, window, cx);
19421        editor.delete_line(&DeleteLine, window, cx);
19422    });
19423    executor.run_until_parked();
19424    cx.assert_state_with_diff(
19425        r#"
19426        use some::mod1;
19427        use some::mod2;
19428
19429        const A: u32 = 42;
19430      + const B: u32 = 42;
19431        ˇ
19432        fn main() {
19433            println!("hello");
19434
19435            println!("world");
19436        }
19437      "#
19438        .unindent(),
19439    );
19440
19441    cx.update_editor(|editor, window, cx| {
19442        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
19443        editor.delete_line(&DeleteLine, window, cx);
19444    });
19445    executor.run_until_parked();
19446    cx.assert_state_with_diff(
19447        r#"
19448        ˇ
19449        fn main() {
19450            println!("hello");
19451
19452            println!("world");
19453        }
19454      "#
19455        .unindent(),
19456    );
19457}
19458
19459#[gpui::test]
19460async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
19461    init_test(cx, |_| {});
19462
19463    let mut cx = EditorTestContext::new(cx).await;
19464    cx.set_head_text(indoc! { "
19465        one
19466        two
19467        three
19468        four
19469        five
19470        "
19471    });
19472    cx.set_state(indoc! { "
19473        one
19474        ˇthree
19475        five
19476    "});
19477    cx.run_until_parked();
19478    cx.update_editor(|editor, window, cx| {
19479        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19480    });
19481    cx.assert_state_with_diff(
19482        indoc! { "
19483        one
19484      - two
19485        ˇthree
19486      - four
19487        five
19488    "}
19489        .to_string(),
19490    );
19491    cx.update_editor(|editor, window, cx| {
19492        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19493    });
19494
19495    cx.assert_state_with_diff(
19496        indoc! { "
19497        one
19498        ˇthree
19499        five
19500    "}
19501        .to_string(),
19502    );
19503
19504    cx.set_state(indoc! { "
19505        one
19506        ˇTWO
19507        three
19508        four
19509        five
19510    "});
19511    cx.run_until_parked();
19512    cx.update_editor(|editor, window, cx| {
19513        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19514    });
19515
19516    cx.assert_state_with_diff(
19517        indoc! { "
19518            one
19519          - two
19520          + ˇTWO
19521            three
19522            four
19523            five
19524        "}
19525        .to_string(),
19526    );
19527    cx.update_editor(|editor, window, cx| {
19528        editor.move_up(&Default::default(), window, cx);
19529        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19530    });
19531    cx.assert_state_with_diff(
19532        indoc! { "
19533            one
19534            ˇTWO
19535            three
19536            four
19537            five
19538        "}
19539        .to_string(),
19540    );
19541}
19542
19543#[gpui::test]
19544async fn test_edits_around_expanded_deletion_hunks(
19545    executor: BackgroundExecutor,
19546    cx: &mut TestAppContext,
19547) {
19548    init_test(cx, |_| {});
19549
19550    let mut cx = EditorTestContext::new(cx).await;
19551
19552    let diff_base = r#"
19553        use some::mod1;
19554        use some::mod2;
19555
19556        const A: u32 = 42;
19557        const B: u32 = 42;
19558        const C: u32 = 42;
19559
19560
19561        fn main() {
19562            println!("hello");
19563
19564            println!("world");
19565        }
19566    "#
19567    .unindent();
19568    executor.run_until_parked();
19569    cx.set_state(
19570        &r#"
19571        use some::mod1;
19572        use some::mod2;
19573
19574        ˇconst B: u32 = 42;
19575        const C: u32 = 42;
19576
19577
19578        fn main() {
19579            println!("hello");
19580
19581            println!("world");
19582        }
19583        "#
19584        .unindent(),
19585    );
19586
19587    cx.set_head_text(&diff_base);
19588    executor.run_until_parked();
19589
19590    cx.update_editor(|editor, window, cx| {
19591        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19592    });
19593    executor.run_until_parked();
19594
19595    cx.assert_state_with_diff(
19596        r#"
19597        use some::mod1;
19598        use some::mod2;
19599
19600      - const A: u32 = 42;
19601        ˇconst B: u32 = 42;
19602        const C: u32 = 42;
19603
19604
19605        fn main() {
19606            println!("hello");
19607
19608            println!("world");
19609        }
19610      "#
19611        .unindent(),
19612    );
19613
19614    cx.update_editor(|editor, window, cx| {
19615        editor.delete_line(&DeleteLine, window, cx);
19616    });
19617    executor.run_until_parked();
19618    cx.assert_state_with_diff(
19619        r#"
19620        use some::mod1;
19621        use some::mod2;
19622
19623      - const A: u32 = 42;
19624      - const B: u32 = 42;
19625        ˇconst C: u32 = 42;
19626
19627
19628        fn main() {
19629            println!("hello");
19630
19631            println!("world");
19632        }
19633      "#
19634        .unindent(),
19635    );
19636
19637    cx.update_editor(|editor, window, cx| {
19638        editor.delete_line(&DeleteLine, window, cx);
19639    });
19640    executor.run_until_parked();
19641    cx.assert_state_with_diff(
19642        r#"
19643        use some::mod1;
19644        use some::mod2;
19645
19646      - const A: u32 = 42;
19647      - const B: u32 = 42;
19648      - const C: u32 = 42;
19649        ˇ
19650
19651        fn main() {
19652            println!("hello");
19653
19654            println!("world");
19655        }
19656      "#
19657        .unindent(),
19658    );
19659
19660    cx.update_editor(|editor, window, cx| {
19661        editor.handle_input("replacement", window, cx);
19662    });
19663    executor.run_until_parked();
19664    cx.assert_state_with_diff(
19665        r#"
19666        use some::mod1;
19667        use some::mod2;
19668
19669      - const A: u32 = 42;
19670      - const B: u32 = 42;
19671      - const C: u32 = 42;
19672      -
19673      + replacementˇ
19674
19675        fn main() {
19676            println!("hello");
19677
19678            println!("world");
19679        }
19680      "#
19681        .unindent(),
19682    );
19683}
19684
19685#[gpui::test]
19686async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19687    init_test(cx, |_| {});
19688
19689    let mut cx = EditorTestContext::new(cx).await;
19690
19691    let base_text = r#"
19692        one
19693        two
19694        three
19695        four
19696        five
19697    "#
19698    .unindent();
19699    executor.run_until_parked();
19700    cx.set_state(
19701        &r#"
19702        one
19703        two
19704        fˇour
19705        five
19706        "#
19707        .unindent(),
19708    );
19709
19710    cx.set_head_text(&base_text);
19711    executor.run_until_parked();
19712
19713    cx.update_editor(|editor, window, cx| {
19714        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19715    });
19716    executor.run_until_parked();
19717
19718    cx.assert_state_with_diff(
19719        r#"
19720          one
19721          two
19722        - three
19723          fˇour
19724          five
19725        "#
19726        .unindent(),
19727    );
19728
19729    cx.update_editor(|editor, window, cx| {
19730        editor.backspace(&Backspace, window, cx);
19731        editor.backspace(&Backspace, window, cx);
19732    });
19733    executor.run_until_parked();
19734    cx.assert_state_with_diff(
19735        r#"
19736          one
19737          two
19738        - threeˇ
19739        - four
19740        + our
19741          five
19742        "#
19743        .unindent(),
19744    );
19745}
19746
19747#[gpui::test]
19748async fn test_edit_after_expanded_modification_hunk(
19749    executor: BackgroundExecutor,
19750    cx: &mut TestAppContext,
19751) {
19752    init_test(cx, |_| {});
19753
19754    let mut cx = EditorTestContext::new(cx).await;
19755
19756    let diff_base = r#"
19757        use some::mod1;
19758        use some::mod2;
19759
19760        const A: u32 = 42;
19761        const B: u32 = 42;
19762        const C: u32 = 42;
19763        const D: u32 = 42;
19764
19765
19766        fn main() {
19767            println!("hello");
19768
19769            println!("world");
19770        }"#
19771    .unindent();
19772
19773    cx.set_state(
19774        &r#"
19775        use some::mod1;
19776        use some::mod2;
19777
19778        const A: u32 = 42;
19779        const B: u32 = 42;
19780        const C: u32 = 43ˇ
19781        const D: u32 = 42;
19782
19783
19784        fn main() {
19785            println!("hello");
19786
19787            println!("world");
19788        }"#
19789        .unindent(),
19790    );
19791
19792    cx.set_head_text(&diff_base);
19793    executor.run_until_parked();
19794    cx.update_editor(|editor, window, cx| {
19795        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19796    });
19797    executor.run_until_parked();
19798
19799    cx.assert_state_with_diff(
19800        r#"
19801        use some::mod1;
19802        use some::mod2;
19803
19804        const A: u32 = 42;
19805        const B: u32 = 42;
19806      - const C: u32 = 42;
19807      + const C: u32 = 43ˇ
19808        const D: u32 = 42;
19809
19810
19811        fn main() {
19812            println!("hello");
19813
19814            println!("world");
19815        }"#
19816        .unindent(),
19817    );
19818
19819    cx.update_editor(|editor, window, cx| {
19820        editor.handle_input("\nnew_line\n", window, cx);
19821    });
19822    executor.run_until_parked();
19823
19824    cx.assert_state_with_diff(
19825        r#"
19826        use some::mod1;
19827        use some::mod2;
19828
19829        const A: u32 = 42;
19830        const B: u32 = 42;
19831      - const C: u32 = 42;
19832      + const C: u32 = 43
19833      + new_line
19834      + ˇ
19835        const D: u32 = 42;
19836
19837
19838        fn main() {
19839            println!("hello");
19840
19841            println!("world");
19842        }"#
19843        .unindent(),
19844    );
19845}
19846
19847#[gpui::test]
19848async fn test_stage_and_unstage_added_file_hunk(
19849    executor: BackgroundExecutor,
19850    cx: &mut TestAppContext,
19851) {
19852    init_test(cx, |_| {});
19853
19854    let mut cx = EditorTestContext::new(cx).await;
19855    cx.update_editor(|editor, _, cx| {
19856        editor.set_expand_all_diff_hunks(cx);
19857    });
19858
19859    let working_copy = r#"
19860            ˇfn main() {
19861                println!("hello, world!");
19862            }
19863        "#
19864    .unindent();
19865
19866    cx.set_state(&working_copy);
19867    executor.run_until_parked();
19868
19869    cx.assert_state_with_diff(
19870        r#"
19871            + ˇfn main() {
19872            +     println!("hello, world!");
19873            + }
19874        "#
19875        .unindent(),
19876    );
19877    cx.assert_index_text(None);
19878
19879    cx.update_editor(|editor, window, cx| {
19880        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19881    });
19882    executor.run_until_parked();
19883    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
19884    cx.assert_state_with_diff(
19885        r#"
19886            + ˇfn main() {
19887            +     println!("hello, world!");
19888            + }
19889        "#
19890        .unindent(),
19891    );
19892
19893    cx.update_editor(|editor, window, cx| {
19894        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19895    });
19896    executor.run_until_parked();
19897    cx.assert_index_text(None);
19898}
19899
19900async fn setup_indent_guides_editor(
19901    text: &str,
19902    cx: &mut TestAppContext,
19903) -> (BufferId, EditorTestContext) {
19904    init_test(cx, |_| {});
19905
19906    let mut cx = EditorTestContext::new(cx).await;
19907
19908    let buffer_id = cx.update_editor(|editor, window, cx| {
19909        editor.set_text(text, window, cx);
19910        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
19911
19912        buffer_ids[0]
19913    });
19914
19915    (buffer_id, cx)
19916}
19917
19918fn assert_indent_guides(
19919    range: Range<u32>,
19920    expected: Vec<IndentGuide>,
19921    active_indices: Option<Vec<usize>>,
19922    cx: &mut EditorTestContext,
19923) {
19924    let indent_guides = cx.update_editor(|editor, window, cx| {
19925        let snapshot = editor.snapshot(window, cx).display_snapshot;
19926        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
19927            editor,
19928            MultiBufferRow(range.start)..MultiBufferRow(range.end),
19929            true,
19930            &snapshot,
19931            cx,
19932        );
19933
19934        indent_guides.sort_by(|a, b| {
19935            a.depth.cmp(&b.depth).then(
19936                a.start_row
19937                    .cmp(&b.start_row)
19938                    .then(a.end_row.cmp(&b.end_row)),
19939            )
19940        });
19941        indent_guides
19942    });
19943
19944    if let Some(expected) = active_indices {
19945        let active_indices = cx.update_editor(|editor, window, cx| {
19946            let snapshot = editor.snapshot(window, cx).display_snapshot;
19947            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
19948        });
19949
19950        assert_eq!(
19951            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
19952            expected,
19953            "Active indent guide indices do not match"
19954        );
19955    }
19956
19957    assert_eq!(indent_guides, expected, "Indent guides do not match");
19958}
19959
19960fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
19961    IndentGuide {
19962        buffer_id,
19963        start_row: MultiBufferRow(start_row),
19964        end_row: MultiBufferRow(end_row),
19965        depth,
19966        tab_size: 4,
19967        settings: IndentGuideSettings {
19968            enabled: true,
19969            line_width: 1,
19970            active_line_width: 1,
19971            coloring: IndentGuideColoring::default(),
19972            background_coloring: IndentGuideBackgroundColoring::default(),
19973        },
19974    }
19975}
19976
19977#[gpui::test]
19978async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
19979    let (buffer_id, mut cx) = setup_indent_guides_editor(
19980        &"
19981        fn main() {
19982            let a = 1;
19983        }"
19984        .unindent(),
19985        cx,
19986    )
19987    .await;
19988
19989    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
19990}
19991
19992#[gpui::test]
19993async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
19994    let (buffer_id, mut cx) = setup_indent_guides_editor(
19995        &"
19996        fn main() {
19997            let a = 1;
19998            let b = 2;
19999        }"
20000        .unindent(),
20001        cx,
20002    )
20003    .await;
20004
20005    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
20006}
20007
20008#[gpui::test]
20009async fn test_indent_guide_nested(cx: &mut TestAppContext) {
20010    let (buffer_id, mut cx) = setup_indent_guides_editor(
20011        &"
20012        fn main() {
20013            let a = 1;
20014            if a == 3 {
20015                let b = 2;
20016            } else {
20017                let c = 3;
20018            }
20019        }"
20020        .unindent(),
20021        cx,
20022    )
20023    .await;
20024
20025    assert_indent_guides(
20026        0..8,
20027        vec![
20028            indent_guide(buffer_id, 1, 6, 0),
20029            indent_guide(buffer_id, 3, 3, 1),
20030            indent_guide(buffer_id, 5, 5, 1),
20031        ],
20032        None,
20033        &mut cx,
20034    );
20035}
20036
20037#[gpui::test]
20038async fn test_indent_guide_tab(cx: &mut TestAppContext) {
20039    let (buffer_id, mut cx) = setup_indent_guides_editor(
20040        &"
20041        fn main() {
20042            let a = 1;
20043                let b = 2;
20044            let c = 3;
20045        }"
20046        .unindent(),
20047        cx,
20048    )
20049    .await;
20050
20051    assert_indent_guides(
20052        0..5,
20053        vec![
20054            indent_guide(buffer_id, 1, 3, 0),
20055            indent_guide(buffer_id, 2, 2, 1),
20056        ],
20057        None,
20058        &mut cx,
20059    );
20060}
20061
20062#[gpui::test]
20063async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
20064    let (buffer_id, mut cx) = setup_indent_guides_editor(
20065        &"
20066        fn main() {
20067            let a = 1;
20068
20069            let c = 3;
20070        }"
20071        .unindent(),
20072        cx,
20073    )
20074    .await;
20075
20076    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
20077}
20078
20079#[gpui::test]
20080async fn test_indent_guide_complex(cx: &mut TestAppContext) {
20081    let (buffer_id, mut cx) = setup_indent_guides_editor(
20082        &"
20083        fn main() {
20084            let a = 1;
20085
20086            let c = 3;
20087
20088            if a == 3 {
20089                let b = 2;
20090            } else {
20091                let c = 3;
20092            }
20093        }"
20094        .unindent(),
20095        cx,
20096    )
20097    .await;
20098
20099    assert_indent_guides(
20100        0..11,
20101        vec![
20102            indent_guide(buffer_id, 1, 9, 0),
20103            indent_guide(buffer_id, 6, 6, 1),
20104            indent_guide(buffer_id, 8, 8, 1),
20105        ],
20106        None,
20107        &mut cx,
20108    );
20109}
20110
20111#[gpui::test]
20112async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
20113    let (buffer_id, mut cx) = setup_indent_guides_editor(
20114        &"
20115        fn main() {
20116            let a = 1;
20117
20118            let c = 3;
20119
20120            if a == 3 {
20121                let b = 2;
20122            } else {
20123                let c = 3;
20124            }
20125        }"
20126        .unindent(),
20127        cx,
20128    )
20129    .await;
20130
20131    assert_indent_guides(
20132        1..11,
20133        vec![
20134            indent_guide(buffer_id, 1, 9, 0),
20135            indent_guide(buffer_id, 6, 6, 1),
20136            indent_guide(buffer_id, 8, 8, 1),
20137        ],
20138        None,
20139        &mut cx,
20140    );
20141}
20142
20143#[gpui::test]
20144async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
20145    let (buffer_id, mut cx) = setup_indent_guides_editor(
20146        &"
20147        fn main() {
20148            let a = 1;
20149
20150            let c = 3;
20151
20152            if a == 3 {
20153                let b = 2;
20154            } else {
20155                let c = 3;
20156            }
20157        }"
20158        .unindent(),
20159        cx,
20160    )
20161    .await;
20162
20163    assert_indent_guides(
20164        1..10,
20165        vec![
20166            indent_guide(buffer_id, 1, 9, 0),
20167            indent_guide(buffer_id, 6, 6, 1),
20168            indent_guide(buffer_id, 8, 8, 1),
20169        ],
20170        None,
20171        &mut cx,
20172    );
20173}
20174
20175#[gpui::test]
20176async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
20177    let (buffer_id, mut cx) = setup_indent_guides_editor(
20178        &"
20179        fn main() {
20180            if a {
20181                b(
20182                    c,
20183                    d,
20184                )
20185            } else {
20186                e(
20187                    f
20188                )
20189            }
20190        }"
20191        .unindent(),
20192        cx,
20193    )
20194    .await;
20195
20196    assert_indent_guides(
20197        0..11,
20198        vec![
20199            indent_guide(buffer_id, 1, 10, 0),
20200            indent_guide(buffer_id, 2, 5, 1),
20201            indent_guide(buffer_id, 7, 9, 1),
20202            indent_guide(buffer_id, 3, 4, 2),
20203            indent_guide(buffer_id, 8, 8, 2),
20204        ],
20205        None,
20206        &mut cx,
20207    );
20208
20209    cx.update_editor(|editor, window, cx| {
20210        editor.fold_at(MultiBufferRow(2), window, cx);
20211        assert_eq!(
20212            editor.display_text(cx),
20213            "
20214            fn main() {
20215                if a {
20216                    b(⋯
20217                    )
20218                } else {
20219                    e(
20220                        f
20221                    )
20222                }
20223            }"
20224            .unindent()
20225        );
20226    });
20227
20228    assert_indent_guides(
20229        0..11,
20230        vec![
20231            indent_guide(buffer_id, 1, 10, 0),
20232            indent_guide(buffer_id, 2, 5, 1),
20233            indent_guide(buffer_id, 7, 9, 1),
20234            indent_guide(buffer_id, 8, 8, 2),
20235        ],
20236        None,
20237        &mut cx,
20238    );
20239}
20240
20241#[gpui::test]
20242async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
20243    let (buffer_id, mut cx) = setup_indent_guides_editor(
20244        &"
20245        block1
20246            block2
20247                block3
20248                    block4
20249            block2
20250        block1
20251        block1"
20252            .unindent(),
20253        cx,
20254    )
20255    .await;
20256
20257    assert_indent_guides(
20258        1..10,
20259        vec![
20260            indent_guide(buffer_id, 1, 4, 0),
20261            indent_guide(buffer_id, 2, 3, 1),
20262            indent_guide(buffer_id, 3, 3, 2),
20263        ],
20264        None,
20265        &mut cx,
20266    );
20267}
20268
20269#[gpui::test]
20270async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
20271    let (buffer_id, mut cx) = setup_indent_guides_editor(
20272        &"
20273        block1
20274            block2
20275                block3
20276
20277        block1
20278        block1"
20279            .unindent(),
20280        cx,
20281    )
20282    .await;
20283
20284    assert_indent_guides(
20285        0..6,
20286        vec![
20287            indent_guide(buffer_id, 1, 2, 0),
20288            indent_guide(buffer_id, 2, 2, 1),
20289        ],
20290        None,
20291        &mut cx,
20292    );
20293}
20294
20295#[gpui::test]
20296async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
20297    let (buffer_id, mut cx) = setup_indent_guides_editor(
20298        &"
20299        function component() {
20300        \treturn (
20301        \t\t\t
20302        \t\t<div>
20303        \t\t\t<abc></abc>
20304        \t\t</div>
20305        \t)
20306        }"
20307        .unindent(),
20308        cx,
20309    )
20310    .await;
20311
20312    assert_indent_guides(
20313        0..8,
20314        vec![
20315            indent_guide(buffer_id, 1, 6, 0),
20316            indent_guide(buffer_id, 2, 5, 1),
20317            indent_guide(buffer_id, 4, 4, 2),
20318        ],
20319        None,
20320        &mut cx,
20321    );
20322}
20323
20324#[gpui::test]
20325async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
20326    let (buffer_id, mut cx) = setup_indent_guides_editor(
20327        &"
20328        function component() {
20329        \treturn (
20330        \t
20331        \t\t<div>
20332        \t\t\t<abc></abc>
20333        \t\t</div>
20334        \t)
20335        }"
20336        .unindent(),
20337        cx,
20338    )
20339    .await;
20340
20341    assert_indent_guides(
20342        0..8,
20343        vec![
20344            indent_guide(buffer_id, 1, 6, 0),
20345            indent_guide(buffer_id, 2, 5, 1),
20346            indent_guide(buffer_id, 4, 4, 2),
20347        ],
20348        None,
20349        &mut cx,
20350    );
20351}
20352
20353#[gpui::test]
20354async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
20355    let (buffer_id, mut cx) = setup_indent_guides_editor(
20356        &"
20357        block1
20358
20359
20360
20361            block2
20362        "
20363        .unindent(),
20364        cx,
20365    )
20366    .await;
20367
20368    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20369}
20370
20371#[gpui::test]
20372async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
20373    let (buffer_id, mut cx) = setup_indent_guides_editor(
20374        &"
20375        def a:
20376        \tb = 3
20377        \tif True:
20378        \t\tc = 4
20379        \t\td = 5
20380        \tprint(b)
20381        "
20382        .unindent(),
20383        cx,
20384    )
20385    .await;
20386
20387    assert_indent_guides(
20388        0..6,
20389        vec![
20390            indent_guide(buffer_id, 1, 5, 0),
20391            indent_guide(buffer_id, 3, 4, 1),
20392        ],
20393        None,
20394        &mut cx,
20395    );
20396}
20397
20398#[gpui::test]
20399async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
20400    let (buffer_id, mut cx) = setup_indent_guides_editor(
20401        &"
20402    fn main() {
20403        let a = 1;
20404    }"
20405        .unindent(),
20406        cx,
20407    )
20408    .await;
20409
20410    cx.update_editor(|editor, window, cx| {
20411        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20412            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20413        });
20414    });
20415
20416    assert_indent_guides(
20417        0..3,
20418        vec![indent_guide(buffer_id, 1, 1, 0)],
20419        Some(vec![0]),
20420        &mut cx,
20421    );
20422}
20423
20424#[gpui::test]
20425async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
20426    let (buffer_id, mut cx) = setup_indent_guides_editor(
20427        &"
20428    fn main() {
20429        if 1 == 2 {
20430            let a = 1;
20431        }
20432    }"
20433        .unindent(),
20434        cx,
20435    )
20436    .await;
20437
20438    cx.update_editor(|editor, window, cx| {
20439        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20440            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20441        });
20442    });
20443
20444    assert_indent_guides(
20445        0..4,
20446        vec![
20447            indent_guide(buffer_id, 1, 3, 0),
20448            indent_guide(buffer_id, 2, 2, 1),
20449        ],
20450        Some(vec![1]),
20451        &mut cx,
20452    );
20453
20454    cx.update_editor(|editor, window, cx| {
20455        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20456            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20457        });
20458    });
20459
20460    assert_indent_guides(
20461        0..4,
20462        vec![
20463            indent_guide(buffer_id, 1, 3, 0),
20464            indent_guide(buffer_id, 2, 2, 1),
20465        ],
20466        Some(vec![1]),
20467        &mut cx,
20468    );
20469
20470    cx.update_editor(|editor, window, cx| {
20471        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20472            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
20473        });
20474    });
20475
20476    assert_indent_guides(
20477        0..4,
20478        vec![
20479            indent_guide(buffer_id, 1, 3, 0),
20480            indent_guide(buffer_id, 2, 2, 1),
20481        ],
20482        Some(vec![0]),
20483        &mut cx,
20484    );
20485}
20486
20487#[gpui::test]
20488async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
20489    let (buffer_id, mut cx) = setup_indent_guides_editor(
20490        &"
20491    fn main() {
20492        let a = 1;
20493
20494        let b = 2;
20495    }"
20496        .unindent(),
20497        cx,
20498    )
20499    .await;
20500
20501    cx.update_editor(|editor, window, cx| {
20502        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20503            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20504        });
20505    });
20506
20507    assert_indent_guides(
20508        0..5,
20509        vec![indent_guide(buffer_id, 1, 3, 0)],
20510        Some(vec![0]),
20511        &mut cx,
20512    );
20513}
20514
20515#[gpui::test]
20516async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
20517    let (buffer_id, mut cx) = setup_indent_guides_editor(
20518        &"
20519    def m:
20520        a = 1
20521        pass"
20522            .unindent(),
20523        cx,
20524    )
20525    .await;
20526
20527    cx.update_editor(|editor, window, cx| {
20528        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20529            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20530        });
20531    });
20532
20533    assert_indent_guides(
20534        0..3,
20535        vec![indent_guide(buffer_id, 1, 2, 0)],
20536        Some(vec![0]),
20537        &mut cx,
20538    );
20539}
20540
20541#[gpui::test]
20542async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
20543    init_test(cx, |_| {});
20544    let mut cx = EditorTestContext::new(cx).await;
20545    let text = indoc! {
20546        "
20547        impl A {
20548            fn b() {
20549                0;
20550                3;
20551                5;
20552                6;
20553                7;
20554            }
20555        }
20556        "
20557    };
20558    let base_text = indoc! {
20559        "
20560        impl A {
20561            fn b() {
20562                0;
20563                1;
20564                2;
20565                3;
20566                4;
20567            }
20568            fn c() {
20569                5;
20570                6;
20571                7;
20572            }
20573        }
20574        "
20575    };
20576
20577    cx.update_editor(|editor, window, cx| {
20578        editor.set_text(text, window, cx);
20579
20580        editor.buffer().update(cx, |multibuffer, cx| {
20581            let buffer = multibuffer.as_singleton().unwrap();
20582            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
20583
20584            multibuffer.set_all_diff_hunks_expanded(cx);
20585            multibuffer.add_diff(diff, cx);
20586
20587            buffer.read(cx).remote_id()
20588        })
20589    });
20590    cx.run_until_parked();
20591
20592    cx.assert_state_with_diff(
20593        indoc! { "
20594          impl A {
20595              fn b() {
20596                  0;
20597        -         1;
20598        -         2;
20599                  3;
20600        -         4;
20601        -     }
20602        -     fn c() {
20603                  5;
20604                  6;
20605                  7;
20606              }
20607          }
20608          ˇ"
20609        }
20610        .to_string(),
20611    );
20612
20613    let mut actual_guides = cx.update_editor(|editor, window, cx| {
20614        editor
20615            .snapshot(window, cx)
20616            .buffer_snapshot
20617            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
20618            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
20619            .collect::<Vec<_>>()
20620    });
20621    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
20622    assert_eq!(
20623        actual_guides,
20624        vec![
20625            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
20626            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
20627            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
20628        ]
20629    );
20630}
20631
20632#[gpui::test]
20633async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
20634    init_test(cx, |_| {});
20635    let mut cx = EditorTestContext::new(cx).await;
20636
20637    let diff_base = r#"
20638        a
20639        b
20640        c
20641        "#
20642    .unindent();
20643
20644    cx.set_state(
20645        &r#"
20646        ˇA
20647        b
20648        C
20649        "#
20650        .unindent(),
20651    );
20652    cx.set_head_text(&diff_base);
20653    cx.update_editor(|editor, window, cx| {
20654        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20655    });
20656    executor.run_until_parked();
20657
20658    let both_hunks_expanded = r#"
20659        - a
20660        + ˇA
20661          b
20662        - c
20663        + C
20664        "#
20665    .unindent();
20666
20667    cx.assert_state_with_diff(both_hunks_expanded.clone());
20668
20669    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20670        let snapshot = editor.snapshot(window, cx);
20671        let hunks = editor
20672            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20673            .collect::<Vec<_>>();
20674        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20675        let buffer_id = hunks[0].buffer_id;
20676        hunks
20677            .into_iter()
20678            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20679            .collect::<Vec<_>>()
20680    });
20681    assert_eq!(hunk_ranges.len(), 2);
20682
20683    cx.update_editor(|editor, _, cx| {
20684        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20685    });
20686    executor.run_until_parked();
20687
20688    let second_hunk_expanded = r#"
20689          ˇA
20690          b
20691        - c
20692        + C
20693        "#
20694    .unindent();
20695
20696    cx.assert_state_with_diff(second_hunk_expanded);
20697
20698    cx.update_editor(|editor, _, cx| {
20699        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20700    });
20701    executor.run_until_parked();
20702
20703    cx.assert_state_with_diff(both_hunks_expanded.clone());
20704
20705    cx.update_editor(|editor, _, cx| {
20706        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20707    });
20708    executor.run_until_parked();
20709
20710    let first_hunk_expanded = r#"
20711        - a
20712        + ˇA
20713          b
20714          C
20715        "#
20716    .unindent();
20717
20718    cx.assert_state_with_diff(first_hunk_expanded);
20719
20720    cx.update_editor(|editor, _, cx| {
20721        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20722    });
20723    executor.run_until_parked();
20724
20725    cx.assert_state_with_diff(both_hunks_expanded);
20726
20727    cx.set_state(
20728        &r#"
20729        ˇA
20730        b
20731        "#
20732        .unindent(),
20733    );
20734    cx.run_until_parked();
20735
20736    // TODO this cursor position seems bad
20737    cx.assert_state_with_diff(
20738        r#"
20739        - ˇa
20740        + A
20741          b
20742        "#
20743        .unindent(),
20744    );
20745
20746    cx.update_editor(|editor, window, cx| {
20747        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20748    });
20749
20750    cx.assert_state_with_diff(
20751        r#"
20752            - ˇa
20753            + A
20754              b
20755            - c
20756            "#
20757        .unindent(),
20758    );
20759
20760    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20761        let snapshot = editor.snapshot(window, cx);
20762        let hunks = editor
20763            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20764            .collect::<Vec<_>>();
20765        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20766        let buffer_id = hunks[0].buffer_id;
20767        hunks
20768            .into_iter()
20769            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20770            .collect::<Vec<_>>()
20771    });
20772    assert_eq!(hunk_ranges.len(), 2);
20773
20774    cx.update_editor(|editor, _, cx| {
20775        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20776    });
20777    executor.run_until_parked();
20778
20779    cx.assert_state_with_diff(
20780        r#"
20781        - ˇa
20782        + A
20783          b
20784        "#
20785        .unindent(),
20786    );
20787}
20788
20789#[gpui::test]
20790async fn test_toggle_deletion_hunk_at_start_of_file(
20791    executor: BackgroundExecutor,
20792    cx: &mut TestAppContext,
20793) {
20794    init_test(cx, |_| {});
20795    let mut cx = EditorTestContext::new(cx).await;
20796
20797    let diff_base = r#"
20798        a
20799        b
20800        c
20801        "#
20802    .unindent();
20803
20804    cx.set_state(
20805        &r#"
20806        ˇb
20807        c
20808        "#
20809        .unindent(),
20810    );
20811    cx.set_head_text(&diff_base);
20812    cx.update_editor(|editor, window, cx| {
20813        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20814    });
20815    executor.run_until_parked();
20816
20817    let hunk_expanded = r#"
20818        - a
20819          ˇb
20820          c
20821        "#
20822    .unindent();
20823
20824    cx.assert_state_with_diff(hunk_expanded.clone());
20825
20826    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20827        let snapshot = editor.snapshot(window, cx);
20828        let hunks = editor
20829            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20830            .collect::<Vec<_>>();
20831        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20832        let buffer_id = hunks[0].buffer_id;
20833        hunks
20834            .into_iter()
20835            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20836            .collect::<Vec<_>>()
20837    });
20838    assert_eq!(hunk_ranges.len(), 1);
20839
20840    cx.update_editor(|editor, _, cx| {
20841        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20842    });
20843    executor.run_until_parked();
20844
20845    let hunk_collapsed = r#"
20846          ˇb
20847          c
20848        "#
20849    .unindent();
20850
20851    cx.assert_state_with_diff(hunk_collapsed);
20852
20853    cx.update_editor(|editor, _, cx| {
20854        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20855    });
20856    executor.run_until_parked();
20857
20858    cx.assert_state_with_diff(hunk_expanded);
20859}
20860
20861#[gpui::test]
20862async fn test_display_diff_hunks(cx: &mut TestAppContext) {
20863    init_test(cx, |_| {});
20864
20865    let fs = FakeFs::new(cx.executor());
20866    fs.insert_tree(
20867        path!("/test"),
20868        json!({
20869            ".git": {},
20870            "file-1": "ONE\n",
20871            "file-2": "TWO\n",
20872            "file-3": "THREE\n",
20873        }),
20874    )
20875    .await;
20876
20877    fs.set_head_for_repo(
20878        path!("/test/.git").as_ref(),
20879        &[
20880            ("file-1".into(), "one\n".into()),
20881            ("file-2".into(), "two\n".into()),
20882            ("file-3".into(), "three\n".into()),
20883        ],
20884        "deadbeef",
20885    );
20886
20887    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
20888    let mut buffers = vec![];
20889    for i in 1..=3 {
20890        let buffer = project
20891            .update(cx, |project, cx| {
20892                let path = format!(path!("/test/file-{}"), i);
20893                project.open_local_buffer(path, cx)
20894            })
20895            .await
20896            .unwrap();
20897        buffers.push(buffer);
20898    }
20899
20900    let multibuffer = cx.new(|cx| {
20901        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
20902        multibuffer.set_all_diff_hunks_expanded(cx);
20903        for buffer in &buffers {
20904            let snapshot = buffer.read(cx).snapshot();
20905            multibuffer.set_excerpts_for_path(
20906                PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
20907                buffer.clone(),
20908                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
20909                2,
20910                cx,
20911            );
20912        }
20913        multibuffer
20914    });
20915
20916    let editor = cx.add_window(|window, cx| {
20917        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
20918    });
20919    cx.run_until_parked();
20920
20921    let snapshot = editor
20922        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
20923        .unwrap();
20924    let hunks = snapshot
20925        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
20926        .map(|hunk| match hunk {
20927            DisplayDiffHunk::Unfolded {
20928                display_row_range, ..
20929            } => display_row_range,
20930            DisplayDiffHunk::Folded { .. } => unreachable!(),
20931        })
20932        .collect::<Vec<_>>();
20933    assert_eq!(
20934        hunks,
20935        [
20936            DisplayRow(2)..DisplayRow(4),
20937            DisplayRow(7)..DisplayRow(9),
20938            DisplayRow(12)..DisplayRow(14),
20939        ]
20940    );
20941}
20942
20943#[gpui::test]
20944async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
20945    init_test(cx, |_| {});
20946
20947    let mut cx = EditorTestContext::new(cx).await;
20948    cx.set_head_text(indoc! { "
20949        one
20950        two
20951        three
20952        four
20953        five
20954        "
20955    });
20956    cx.set_index_text(indoc! { "
20957        one
20958        two
20959        three
20960        four
20961        five
20962        "
20963    });
20964    cx.set_state(indoc! {"
20965        one
20966        TWO
20967        ˇTHREE
20968        FOUR
20969        five
20970    "});
20971    cx.run_until_parked();
20972    cx.update_editor(|editor, window, cx| {
20973        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20974    });
20975    cx.run_until_parked();
20976    cx.assert_index_text(Some(indoc! {"
20977        one
20978        TWO
20979        THREE
20980        FOUR
20981        five
20982    "}));
20983    cx.set_state(indoc! { "
20984        one
20985        TWO
20986        ˇTHREE-HUNDRED
20987        FOUR
20988        five
20989    "});
20990    cx.run_until_parked();
20991    cx.update_editor(|editor, window, cx| {
20992        let snapshot = editor.snapshot(window, cx);
20993        let hunks = editor
20994            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20995            .collect::<Vec<_>>();
20996        assert_eq!(hunks.len(), 1);
20997        assert_eq!(
20998            hunks[0].status(),
20999            DiffHunkStatus {
21000                kind: DiffHunkStatusKind::Modified,
21001                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
21002            }
21003        );
21004
21005        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21006    });
21007    cx.run_until_parked();
21008    cx.assert_index_text(Some(indoc! {"
21009        one
21010        TWO
21011        THREE-HUNDRED
21012        FOUR
21013        five
21014    "}));
21015}
21016
21017#[gpui::test]
21018fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
21019    init_test(cx, |_| {});
21020
21021    let editor = cx.add_window(|window, cx| {
21022        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
21023        build_editor(buffer, window, cx)
21024    });
21025
21026    let render_args = Arc::new(Mutex::new(None));
21027    let snapshot = editor
21028        .update(cx, |editor, window, cx| {
21029            let snapshot = editor.buffer().read(cx).snapshot(cx);
21030            let range =
21031                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
21032
21033            struct RenderArgs {
21034                row: MultiBufferRow,
21035                folded: bool,
21036                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
21037            }
21038
21039            let crease = Crease::inline(
21040                range,
21041                FoldPlaceholder::test(),
21042                {
21043                    let toggle_callback = render_args.clone();
21044                    move |row, folded, callback, _window, _cx| {
21045                        *toggle_callback.lock() = Some(RenderArgs {
21046                            row,
21047                            folded,
21048                            callback,
21049                        });
21050                        div()
21051                    }
21052                },
21053                |_row, _folded, _window, _cx| div(),
21054            );
21055
21056            editor.insert_creases(Some(crease), cx);
21057            let snapshot = editor.snapshot(window, cx);
21058            let _div =
21059                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
21060            snapshot
21061        })
21062        .unwrap();
21063
21064    let render_args = render_args.lock().take().unwrap();
21065    assert_eq!(render_args.row, MultiBufferRow(1));
21066    assert!(!render_args.folded);
21067    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21068
21069    cx.update_window(*editor, |_, window, cx| {
21070        (render_args.callback)(true, window, cx)
21071    })
21072    .unwrap();
21073    let snapshot = editor
21074        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21075        .unwrap();
21076    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
21077
21078    cx.update_window(*editor, |_, window, cx| {
21079        (render_args.callback)(false, window, cx)
21080    })
21081    .unwrap();
21082    let snapshot = editor
21083        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21084        .unwrap();
21085    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21086}
21087
21088#[gpui::test]
21089async fn test_input_text(cx: &mut TestAppContext) {
21090    init_test(cx, |_| {});
21091    let mut cx = EditorTestContext::new(cx).await;
21092
21093    cx.set_state(
21094        &r#"ˇone
21095        two
21096
21097        three
21098        fourˇ
21099        five
21100
21101        siˇx"#
21102            .unindent(),
21103    );
21104
21105    cx.dispatch_action(HandleInput(String::new()));
21106    cx.assert_editor_state(
21107        &r#"ˇone
21108        two
21109
21110        three
21111        fourˇ
21112        five
21113
21114        siˇx"#
21115            .unindent(),
21116    );
21117
21118    cx.dispatch_action(HandleInput("AAAA".to_string()));
21119    cx.assert_editor_state(
21120        &r#"AAAAˇone
21121        two
21122
21123        three
21124        fourAAAAˇ
21125        five
21126
21127        siAAAAˇx"#
21128            .unindent(),
21129    );
21130}
21131
21132#[gpui::test]
21133async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
21134    init_test(cx, |_| {});
21135
21136    let mut cx = EditorTestContext::new(cx).await;
21137    cx.set_state(
21138        r#"let foo = 1;
21139let foo = 2;
21140let foo = 3;
21141let fooˇ = 4;
21142let foo = 5;
21143let foo = 6;
21144let foo = 7;
21145let foo = 8;
21146let foo = 9;
21147let foo = 10;
21148let foo = 11;
21149let foo = 12;
21150let foo = 13;
21151let foo = 14;
21152let foo = 15;"#,
21153    );
21154
21155    cx.update_editor(|e, window, cx| {
21156        assert_eq!(
21157            e.next_scroll_position,
21158            NextScrollCursorCenterTopBottom::Center,
21159            "Default next scroll direction is center",
21160        );
21161
21162        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21163        assert_eq!(
21164            e.next_scroll_position,
21165            NextScrollCursorCenterTopBottom::Top,
21166            "After center, next scroll direction should be top",
21167        );
21168
21169        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21170        assert_eq!(
21171            e.next_scroll_position,
21172            NextScrollCursorCenterTopBottom::Bottom,
21173            "After top, next scroll direction should be bottom",
21174        );
21175
21176        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21177        assert_eq!(
21178            e.next_scroll_position,
21179            NextScrollCursorCenterTopBottom::Center,
21180            "After bottom, scrolling should start over",
21181        );
21182
21183        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21184        assert_eq!(
21185            e.next_scroll_position,
21186            NextScrollCursorCenterTopBottom::Top,
21187            "Scrolling continues if retriggered fast enough"
21188        );
21189    });
21190
21191    cx.executor()
21192        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
21193    cx.executor().run_until_parked();
21194    cx.update_editor(|e, _, _| {
21195        assert_eq!(
21196            e.next_scroll_position,
21197            NextScrollCursorCenterTopBottom::Center,
21198            "If scrolling is not triggered fast enough, it should reset"
21199        );
21200    });
21201}
21202
21203#[gpui::test]
21204async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
21205    init_test(cx, |_| {});
21206    let mut cx = EditorLspTestContext::new_rust(
21207        lsp::ServerCapabilities {
21208            definition_provider: Some(lsp::OneOf::Left(true)),
21209            references_provider: Some(lsp::OneOf::Left(true)),
21210            ..lsp::ServerCapabilities::default()
21211        },
21212        cx,
21213    )
21214    .await;
21215
21216    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
21217        let go_to_definition = cx
21218            .lsp
21219            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21220                move |params, _| async move {
21221                    if empty_go_to_definition {
21222                        Ok(None)
21223                    } else {
21224                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
21225                            uri: params.text_document_position_params.text_document.uri,
21226                            range: lsp::Range::new(
21227                                lsp::Position::new(4, 3),
21228                                lsp::Position::new(4, 6),
21229                            ),
21230                        })))
21231                    }
21232                },
21233            );
21234        let references = cx
21235            .lsp
21236            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21237                Ok(Some(vec![lsp::Location {
21238                    uri: params.text_document_position.text_document.uri,
21239                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
21240                }]))
21241            });
21242        (go_to_definition, references)
21243    };
21244
21245    cx.set_state(
21246        &r#"fn one() {
21247            let mut a = ˇtwo();
21248        }
21249
21250        fn two() {}"#
21251            .unindent(),
21252    );
21253    set_up_lsp_handlers(false, &mut cx);
21254    let navigated = cx
21255        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21256        .await
21257        .expect("Failed to navigate to definition");
21258    assert_eq!(
21259        navigated,
21260        Navigated::Yes,
21261        "Should have navigated to definition from the GetDefinition response"
21262    );
21263    cx.assert_editor_state(
21264        &r#"fn one() {
21265            let mut a = two();
21266        }
21267
21268        fn «twoˇ»() {}"#
21269            .unindent(),
21270    );
21271
21272    let editors = cx.update_workspace(|workspace, _, cx| {
21273        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21274    });
21275    cx.update_editor(|_, _, test_editor_cx| {
21276        assert_eq!(
21277            editors.len(),
21278            1,
21279            "Initially, only one, test, editor should be open in the workspace"
21280        );
21281        assert_eq!(
21282            test_editor_cx.entity(),
21283            editors.last().expect("Asserted len is 1").clone()
21284        );
21285    });
21286
21287    set_up_lsp_handlers(true, &mut cx);
21288    let navigated = cx
21289        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21290        .await
21291        .expect("Failed to navigate to lookup references");
21292    assert_eq!(
21293        navigated,
21294        Navigated::Yes,
21295        "Should have navigated to references as a fallback after empty GoToDefinition response"
21296    );
21297    // We should not change the selections in the existing file,
21298    // if opening another milti buffer with the references
21299    cx.assert_editor_state(
21300        &r#"fn one() {
21301            let mut a = two();
21302        }
21303
21304        fn «twoˇ»() {}"#
21305            .unindent(),
21306    );
21307    let editors = cx.update_workspace(|workspace, _, cx| {
21308        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21309    });
21310    cx.update_editor(|_, _, test_editor_cx| {
21311        assert_eq!(
21312            editors.len(),
21313            2,
21314            "After falling back to references search, we open a new editor with the results"
21315        );
21316        let references_fallback_text = editors
21317            .into_iter()
21318            .find(|new_editor| *new_editor != test_editor_cx.entity())
21319            .expect("Should have one non-test editor now")
21320            .read(test_editor_cx)
21321            .text(test_editor_cx);
21322        assert_eq!(
21323            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
21324            "Should use the range from the references response and not the GoToDefinition one"
21325        );
21326    });
21327}
21328
21329#[gpui::test]
21330async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
21331    init_test(cx, |_| {});
21332    cx.update(|cx| {
21333        let mut editor_settings = EditorSettings::get_global(cx).clone();
21334        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
21335        EditorSettings::override_global(editor_settings, cx);
21336    });
21337    let mut cx = EditorLspTestContext::new_rust(
21338        lsp::ServerCapabilities {
21339            definition_provider: Some(lsp::OneOf::Left(true)),
21340            references_provider: Some(lsp::OneOf::Left(true)),
21341            ..lsp::ServerCapabilities::default()
21342        },
21343        cx,
21344    )
21345    .await;
21346    let original_state = r#"fn one() {
21347        let mut a = ˇtwo();
21348    }
21349
21350    fn two() {}"#
21351        .unindent();
21352    cx.set_state(&original_state);
21353
21354    let mut go_to_definition = cx
21355        .lsp
21356        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21357            move |_, _| async move { Ok(None) },
21358        );
21359    let _references = cx
21360        .lsp
21361        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
21362            panic!("Should not call for references with no go to definition fallback")
21363        });
21364
21365    let navigated = cx
21366        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21367        .await
21368        .expect("Failed to navigate to lookup references");
21369    go_to_definition
21370        .next()
21371        .await
21372        .expect("Should have called the go_to_definition handler");
21373
21374    assert_eq!(
21375        navigated,
21376        Navigated::No,
21377        "Should have navigated to references as a fallback after empty GoToDefinition response"
21378    );
21379    cx.assert_editor_state(&original_state);
21380    let editors = cx.update_workspace(|workspace, _, cx| {
21381        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21382    });
21383    cx.update_editor(|_, _, _| {
21384        assert_eq!(
21385            editors.len(),
21386            1,
21387            "After unsuccessful fallback, no other editor should have been opened"
21388        );
21389    });
21390}
21391
21392#[gpui::test]
21393async fn test_find_all_references_editor_reuse(cx: &mut TestAppContext) {
21394    init_test(cx, |_| {});
21395    let mut cx = EditorLspTestContext::new_rust(
21396        lsp::ServerCapabilities {
21397            references_provider: Some(lsp::OneOf::Left(true)),
21398            ..lsp::ServerCapabilities::default()
21399        },
21400        cx,
21401    )
21402    .await;
21403
21404    cx.set_state(
21405        &r#"
21406        fn one() {
21407            let mut a = two();
21408        }
21409
21410        fn ˇtwo() {}"#
21411            .unindent(),
21412    );
21413    cx.lsp
21414        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21415            Ok(Some(vec![
21416                lsp::Location {
21417                    uri: params.text_document_position.text_document.uri.clone(),
21418                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
21419                },
21420                lsp::Location {
21421                    uri: params.text_document_position.text_document.uri,
21422                    range: lsp::Range::new(lsp::Position::new(4, 4), lsp::Position::new(4, 7)),
21423                },
21424            ]))
21425        });
21426    let navigated = cx
21427        .update_editor(|editor, window, cx| {
21428            editor.find_all_references(&FindAllReferences, window, cx)
21429        })
21430        .unwrap()
21431        .await
21432        .expect("Failed to navigate to references");
21433    assert_eq!(
21434        navigated,
21435        Navigated::Yes,
21436        "Should have navigated to references from the FindAllReferences response"
21437    );
21438    cx.assert_editor_state(
21439        &r#"fn one() {
21440            let mut a = two();
21441        }
21442
21443        fn ˇtwo() {}"#
21444            .unindent(),
21445    );
21446
21447    let editors = cx.update_workspace(|workspace, _, cx| {
21448        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21449    });
21450    cx.update_editor(|_, _, _| {
21451        assert_eq!(editors.len(), 2, "We should have opened a new multibuffer");
21452    });
21453
21454    cx.set_state(
21455        &r#"fn one() {
21456            let mut a = ˇtwo();
21457        }
21458
21459        fn two() {}"#
21460            .unindent(),
21461    );
21462    let navigated = cx
21463        .update_editor(|editor, window, cx| {
21464            editor.find_all_references(&FindAllReferences, window, cx)
21465        })
21466        .unwrap()
21467        .await
21468        .expect("Failed to navigate to references");
21469    assert_eq!(
21470        navigated,
21471        Navigated::Yes,
21472        "Should have navigated to references from the FindAllReferences response"
21473    );
21474    cx.assert_editor_state(
21475        &r#"fn one() {
21476            let mut a = ˇtwo();
21477        }
21478
21479        fn two() {}"#
21480            .unindent(),
21481    );
21482    let editors = cx.update_workspace(|workspace, _, cx| {
21483        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21484    });
21485    cx.update_editor(|_, _, _| {
21486        assert_eq!(
21487            editors.len(),
21488            2,
21489            "should have re-used the previous multibuffer"
21490        );
21491    });
21492
21493    cx.set_state(
21494        &r#"fn one() {
21495            let mut a = ˇtwo();
21496        }
21497        fn three() {}
21498        fn two() {}"#
21499            .unindent(),
21500    );
21501    cx.lsp
21502        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21503            Ok(Some(vec![
21504                lsp::Location {
21505                    uri: params.text_document_position.text_document.uri.clone(),
21506                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
21507                },
21508                lsp::Location {
21509                    uri: params.text_document_position.text_document.uri,
21510                    range: lsp::Range::new(lsp::Position::new(5, 4), lsp::Position::new(5, 7)),
21511                },
21512            ]))
21513        });
21514    let navigated = cx
21515        .update_editor(|editor, window, cx| {
21516            editor.find_all_references(&FindAllReferences, window, cx)
21517        })
21518        .unwrap()
21519        .await
21520        .expect("Failed to navigate to references");
21521    assert_eq!(
21522        navigated,
21523        Navigated::Yes,
21524        "Should have navigated to references from the FindAllReferences response"
21525    );
21526    cx.assert_editor_state(
21527        &r#"fn one() {
21528                let mut a = ˇtwo();
21529            }
21530            fn three() {}
21531            fn two() {}"#
21532            .unindent(),
21533    );
21534    let editors = cx.update_workspace(|workspace, _, cx| {
21535        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21536    });
21537    cx.update_editor(|_, _, _| {
21538        assert_eq!(
21539            editors.len(),
21540            3,
21541            "should have used a new multibuffer as offsets changed"
21542        );
21543    });
21544}
21545#[gpui::test]
21546async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
21547    init_test(cx, |_| {});
21548
21549    let language = Arc::new(Language::new(
21550        LanguageConfig::default(),
21551        Some(tree_sitter_rust::LANGUAGE.into()),
21552    ));
21553
21554    let text = r#"
21555        #[cfg(test)]
21556        mod tests() {
21557            #[test]
21558            fn runnable_1() {
21559                let a = 1;
21560            }
21561
21562            #[test]
21563            fn runnable_2() {
21564                let a = 1;
21565                let b = 2;
21566            }
21567        }
21568    "#
21569    .unindent();
21570
21571    let fs = FakeFs::new(cx.executor());
21572    fs.insert_file("/file.rs", Default::default()).await;
21573
21574    let project = Project::test(fs, ["/a".as_ref()], cx).await;
21575    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21576    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21577    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
21578    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
21579
21580    let editor = cx.new_window_entity(|window, cx| {
21581        Editor::new(
21582            EditorMode::full(),
21583            multi_buffer,
21584            Some(project.clone()),
21585            window,
21586            cx,
21587        )
21588    });
21589
21590    editor.update_in(cx, |editor, window, cx| {
21591        let snapshot = editor.buffer().read(cx).snapshot(cx);
21592        editor.tasks.insert(
21593            (buffer.read(cx).remote_id(), 3),
21594            RunnableTasks {
21595                templates: vec![],
21596                offset: snapshot.anchor_before(43),
21597                column: 0,
21598                extra_variables: HashMap::default(),
21599                context_range: BufferOffset(43)..BufferOffset(85),
21600            },
21601        );
21602        editor.tasks.insert(
21603            (buffer.read(cx).remote_id(), 8),
21604            RunnableTasks {
21605                templates: vec![],
21606                offset: snapshot.anchor_before(86),
21607                column: 0,
21608                extra_variables: HashMap::default(),
21609                context_range: BufferOffset(86)..BufferOffset(191),
21610            },
21611        );
21612
21613        // Test finding task when cursor is inside function body
21614        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21615            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
21616        });
21617        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
21618        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
21619
21620        // Test finding task when cursor is on function name
21621        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21622            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
21623        });
21624        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
21625        assert_eq!(row, 8, "Should find task when cursor is on function name");
21626    });
21627}
21628
21629#[gpui::test]
21630async fn test_folding_buffers(cx: &mut TestAppContext) {
21631    init_test(cx, |_| {});
21632
21633    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
21634    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
21635    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
21636
21637    let fs = FakeFs::new(cx.executor());
21638    fs.insert_tree(
21639        path!("/a"),
21640        json!({
21641            "first.rs": sample_text_1,
21642            "second.rs": sample_text_2,
21643            "third.rs": sample_text_3,
21644        }),
21645    )
21646    .await;
21647    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21648    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21649    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21650    let worktree = project.update(cx, |project, cx| {
21651        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
21652        assert_eq!(worktrees.len(), 1);
21653        worktrees.pop().unwrap()
21654    });
21655    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
21656
21657    let buffer_1 = project
21658        .update(cx, |project, cx| {
21659            project.open_buffer((worktree_id, "first.rs"), cx)
21660        })
21661        .await
21662        .unwrap();
21663    let buffer_2 = project
21664        .update(cx, |project, cx| {
21665            project.open_buffer((worktree_id, "second.rs"), cx)
21666        })
21667        .await
21668        .unwrap();
21669    let buffer_3 = project
21670        .update(cx, |project, cx| {
21671            project.open_buffer((worktree_id, "third.rs"), cx)
21672        })
21673        .await
21674        .unwrap();
21675
21676    let multi_buffer = cx.new(|cx| {
21677        let mut multi_buffer = MultiBuffer::new(ReadWrite);
21678        multi_buffer.push_excerpts(
21679            buffer_1.clone(),
21680            [
21681                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21682                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21683                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21684            ],
21685            cx,
21686        );
21687        multi_buffer.push_excerpts(
21688            buffer_2.clone(),
21689            [
21690                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21691                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21692                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21693            ],
21694            cx,
21695        );
21696        multi_buffer.push_excerpts(
21697            buffer_3.clone(),
21698            [
21699                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21700                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21701                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21702            ],
21703            cx,
21704        );
21705        multi_buffer
21706    });
21707    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
21708        Editor::new(
21709            EditorMode::full(),
21710            multi_buffer.clone(),
21711            Some(project.clone()),
21712            window,
21713            cx,
21714        )
21715    });
21716
21717    assert_eq!(
21718        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21719        "\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",
21720    );
21721
21722    multi_buffer_editor.update(cx, |editor, cx| {
21723        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
21724    });
21725    assert_eq!(
21726        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21727        "\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",
21728        "After folding the first buffer, its text should not be displayed"
21729    );
21730
21731    multi_buffer_editor.update(cx, |editor, cx| {
21732        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
21733    });
21734    assert_eq!(
21735        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21736        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
21737        "After folding the second buffer, its text should not be displayed"
21738    );
21739
21740    multi_buffer_editor.update(cx, |editor, cx| {
21741        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
21742    });
21743    assert_eq!(
21744        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21745        "\n\n\n\n\n",
21746        "After folding the third buffer, its text should not be displayed"
21747    );
21748
21749    // Emulate selection inside the fold logic, that should work
21750    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21751        editor
21752            .snapshot(window, cx)
21753            .next_line_boundary(Point::new(0, 4));
21754    });
21755
21756    multi_buffer_editor.update(cx, |editor, cx| {
21757        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
21758    });
21759    assert_eq!(
21760        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21761        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
21762        "After unfolding the second buffer, its text should be displayed"
21763    );
21764
21765    // Typing inside of buffer 1 causes that buffer to be unfolded.
21766    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21767        assert_eq!(
21768            multi_buffer
21769                .read(cx)
21770                .snapshot(cx)
21771                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
21772                .collect::<String>(),
21773            "bbbb"
21774        );
21775        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21776            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
21777        });
21778        editor.handle_input("B", window, cx);
21779    });
21780
21781    assert_eq!(
21782        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21783        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
21784        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
21785    );
21786
21787    multi_buffer_editor.update(cx, |editor, cx| {
21788        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
21789    });
21790    assert_eq!(
21791        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21792        "\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",
21793        "After unfolding the all buffers, all original text should be displayed"
21794    );
21795}
21796
21797#[gpui::test]
21798async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
21799    init_test(cx, |_| {});
21800
21801    let sample_text_1 = "1111\n2222\n3333".to_string();
21802    let sample_text_2 = "4444\n5555\n6666".to_string();
21803    let sample_text_3 = "7777\n8888\n9999".to_string();
21804
21805    let fs = FakeFs::new(cx.executor());
21806    fs.insert_tree(
21807        path!("/a"),
21808        json!({
21809            "first.rs": sample_text_1,
21810            "second.rs": sample_text_2,
21811            "third.rs": sample_text_3,
21812        }),
21813    )
21814    .await;
21815    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21816    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21817    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21818    let worktree = project.update(cx, |project, cx| {
21819        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
21820        assert_eq!(worktrees.len(), 1);
21821        worktrees.pop().unwrap()
21822    });
21823    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
21824
21825    let buffer_1 = project
21826        .update(cx, |project, cx| {
21827            project.open_buffer((worktree_id, "first.rs"), cx)
21828        })
21829        .await
21830        .unwrap();
21831    let buffer_2 = project
21832        .update(cx, |project, cx| {
21833            project.open_buffer((worktree_id, "second.rs"), cx)
21834        })
21835        .await
21836        .unwrap();
21837    let buffer_3 = project
21838        .update(cx, |project, cx| {
21839            project.open_buffer((worktree_id, "third.rs"), cx)
21840        })
21841        .await
21842        .unwrap();
21843
21844    let multi_buffer = cx.new(|cx| {
21845        let mut multi_buffer = MultiBuffer::new(ReadWrite);
21846        multi_buffer.push_excerpts(
21847            buffer_1.clone(),
21848            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21849            cx,
21850        );
21851        multi_buffer.push_excerpts(
21852            buffer_2.clone(),
21853            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21854            cx,
21855        );
21856        multi_buffer.push_excerpts(
21857            buffer_3.clone(),
21858            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21859            cx,
21860        );
21861        multi_buffer
21862    });
21863
21864    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
21865        Editor::new(
21866            EditorMode::full(),
21867            multi_buffer,
21868            Some(project.clone()),
21869            window,
21870            cx,
21871        )
21872    });
21873
21874    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
21875    assert_eq!(
21876        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21877        full_text,
21878    );
21879
21880    multi_buffer_editor.update(cx, |editor, cx| {
21881        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
21882    });
21883    assert_eq!(
21884        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21885        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
21886        "After folding the first buffer, its text should not be displayed"
21887    );
21888
21889    multi_buffer_editor.update(cx, |editor, cx| {
21890        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
21891    });
21892
21893    assert_eq!(
21894        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21895        "\n\n\n\n\n\n7777\n8888\n9999",
21896        "After folding the second buffer, its text should not be displayed"
21897    );
21898
21899    multi_buffer_editor.update(cx, |editor, cx| {
21900        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
21901    });
21902    assert_eq!(
21903        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21904        "\n\n\n\n\n",
21905        "After folding the third buffer, its text should not be displayed"
21906    );
21907
21908    multi_buffer_editor.update(cx, |editor, cx| {
21909        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
21910    });
21911    assert_eq!(
21912        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21913        "\n\n\n\n4444\n5555\n6666\n\n",
21914        "After unfolding the second buffer, its text should be displayed"
21915    );
21916
21917    multi_buffer_editor.update(cx, |editor, cx| {
21918        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
21919    });
21920    assert_eq!(
21921        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21922        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
21923        "After unfolding the first buffer, its text should be displayed"
21924    );
21925
21926    multi_buffer_editor.update(cx, |editor, cx| {
21927        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
21928    });
21929    assert_eq!(
21930        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21931        full_text,
21932        "After unfolding all buffers, all original text should be displayed"
21933    );
21934}
21935
21936#[gpui::test]
21937async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
21938    init_test(cx, |_| {});
21939
21940    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
21941
21942    let fs = FakeFs::new(cx.executor());
21943    fs.insert_tree(
21944        path!("/a"),
21945        json!({
21946            "main.rs": sample_text,
21947        }),
21948    )
21949    .await;
21950    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21951    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21952    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21953    let worktree = project.update(cx, |project, cx| {
21954        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
21955        assert_eq!(worktrees.len(), 1);
21956        worktrees.pop().unwrap()
21957    });
21958    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
21959
21960    let buffer_1 = project
21961        .update(cx, |project, cx| {
21962            project.open_buffer((worktree_id, "main.rs"), cx)
21963        })
21964        .await
21965        .unwrap();
21966
21967    let multi_buffer = cx.new(|cx| {
21968        let mut multi_buffer = MultiBuffer::new(ReadWrite);
21969        multi_buffer.push_excerpts(
21970            buffer_1.clone(),
21971            [ExcerptRange::new(
21972                Point::new(0, 0)
21973                    ..Point::new(
21974                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
21975                        0,
21976                    ),
21977            )],
21978            cx,
21979        );
21980        multi_buffer
21981    });
21982    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
21983        Editor::new(
21984            EditorMode::full(),
21985            multi_buffer,
21986            Some(project.clone()),
21987            window,
21988            cx,
21989        )
21990    });
21991
21992    let selection_range = Point::new(1, 0)..Point::new(2, 0);
21993    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21994        enum TestHighlight {}
21995        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
21996        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
21997        editor.highlight_text::<TestHighlight>(
21998            vec![highlight_range.clone()],
21999            HighlightStyle::color(Hsla::green()),
22000            cx,
22001        );
22002        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22003            s.select_ranges(Some(highlight_range))
22004        });
22005    });
22006
22007    let full_text = format!("\n\n{sample_text}");
22008    assert_eq!(
22009        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22010        full_text,
22011    );
22012}
22013
22014#[gpui::test]
22015async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
22016    init_test(cx, |_| {});
22017    cx.update(|cx| {
22018        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
22019            "keymaps/default-linux.json",
22020            cx,
22021        )
22022        .unwrap();
22023        cx.bind_keys(default_key_bindings);
22024    });
22025
22026    let (editor, cx) = cx.add_window_view(|window, cx| {
22027        let multi_buffer = MultiBuffer::build_multi(
22028            [
22029                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
22030                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
22031                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
22032                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
22033            ],
22034            cx,
22035        );
22036        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
22037
22038        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
22039        // fold all but the second buffer, so that we test navigating between two
22040        // adjacent folded buffers, as well as folded buffers at the start and
22041        // end the multibuffer
22042        editor.fold_buffer(buffer_ids[0], cx);
22043        editor.fold_buffer(buffer_ids[2], cx);
22044        editor.fold_buffer(buffer_ids[3], cx);
22045
22046        editor
22047    });
22048    cx.simulate_resize(size(px(1000.), px(1000.)));
22049
22050    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
22051    cx.assert_excerpts_with_selections(indoc! {"
22052        [EXCERPT]
22053        ˇ[FOLDED]
22054        [EXCERPT]
22055        a1
22056        b1
22057        [EXCERPT]
22058        [FOLDED]
22059        [EXCERPT]
22060        [FOLDED]
22061        "
22062    });
22063    cx.simulate_keystroke("down");
22064    cx.assert_excerpts_with_selections(indoc! {"
22065        [EXCERPT]
22066        [FOLDED]
22067        [EXCERPT]
22068        ˇa1
22069        b1
22070        [EXCERPT]
22071        [FOLDED]
22072        [EXCERPT]
22073        [FOLDED]
22074        "
22075    });
22076    cx.simulate_keystroke("down");
22077    cx.assert_excerpts_with_selections(indoc! {"
22078        [EXCERPT]
22079        [FOLDED]
22080        [EXCERPT]
22081        a1
22082        ˇb1
22083        [EXCERPT]
22084        [FOLDED]
22085        [EXCERPT]
22086        [FOLDED]
22087        "
22088    });
22089    cx.simulate_keystroke("down");
22090    cx.assert_excerpts_with_selections(indoc! {"
22091        [EXCERPT]
22092        [FOLDED]
22093        [EXCERPT]
22094        a1
22095        b1
22096        ˇ[EXCERPT]
22097        [FOLDED]
22098        [EXCERPT]
22099        [FOLDED]
22100        "
22101    });
22102    cx.simulate_keystroke("down");
22103    cx.assert_excerpts_with_selections(indoc! {"
22104        [EXCERPT]
22105        [FOLDED]
22106        [EXCERPT]
22107        a1
22108        b1
22109        [EXCERPT]
22110        ˇ[FOLDED]
22111        [EXCERPT]
22112        [FOLDED]
22113        "
22114    });
22115    for _ in 0..5 {
22116        cx.simulate_keystroke("down");
22117        cx.assert_excerpts_with_selections(indoc! {"
22118            [EXCERPT]
22119            [FOLDED]
22120            [EXCERPT]
22121            a1
22122            b1
22123            [EXCERPT]
22124            [FOLDED]
22125            [EXCERPT]
22126            ˇ[FOLDED]
22127            "
22128        });
22129    }
22130
22131    cx.simulate_keystroke("up");
22132    cx.assert_excerpts_with_selections(indoc! {"
22133        [EXCERPT]
22134        [FOLDED]
22135        [EXCERPT]
22136        a1
22137        b1
22138        [EXCERPT]
22139        ˇ[FOLDED]
22140        [EXCERPT]
22141        [FOLDED]
22142        "
22143    });
22144    cx.simulate_keystroke("up");
22145    cx.assert_excerpts_with_selections(indoc! {"
22146        [EXCERPT]
22147        [FOLDED]
22148        [EXCERPT]
22149        a1
22150        b1
22151        ˇ[EXCERPT]
22152        [FOLDED]
22153        [EXCERPT]
22154        [FOLDED]
22155        "
22156    });
22157    cx.simulate_keystroke("up");
22158    cx.assert_excerpts_with_selections(indoc! {"
22159        [EXCERPT]
22160        [FOLDED]
22161        [EXCERPT]
22162        a1
22163        ˇb1
22164        [EXCERPT]
22165        [FOLDED]
22166        [EXCERPT]
22167        [FOLDED]
22168        "
22169    });
22170    cx.simulate_keystroke("up");
22171    cx.assert_excerpts_with_selections(indoc! {"
22172        [EXCERPT]
22173        [FOLDED]
22174        [EXCERPT]
22175        ˇa1
22176        b1
22177        [EXCERPT]
22178        [FOLDED]
22179        [EXCERPT]
22180        [FOLDED]
22181        "
22182    });
22183    for _ in 0..5 {
22184        cx.simulate_keystroke("up");
22185        cx.assert_excerpts_with_selections(indoc! {"
22186            [EXCERPT]
22187            ˇ[FOLDED]
22188            [EXCERPT]
22189            a1
22190            b1
22191            [EXCERPT]
22192            [FOLDED]
22193            [EXCERPT]
22194            [FOLDED]
22195            "
22196        });
22197    }
22198}
22199
22200#[gpui::test]
22201async fn test_edit_prediction_text(cx: &mut TestAppContext) {
22202    init_test(cx, |_| {});
22203
22204    // Simple insertion
22205    assert_highlighted_edits(
22206        "Hello, world!",
22207        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
22208        true,
22209        cx,
22210        |highlighted_edits, cx| {
22211            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
22212            assert_eq!(highlighted_edits.highlights.len(), 1);
22213            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
22214            assert_eq!(
22215                highlighted_edits.highlights[0].1.background_color,
22216                Some(cx.theme().status().created_background)
22217            );
22218        },
22219    )
22220    .await;
22221
22222    // Replacement
22223    assert_highlighted_edits(
22224        "This is a test.",
22225        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
22226        false,
22227        cx,
22228        |highlighted_edits, cx| {
22229            assert_eq!(highlighted_edits.text, "That is a test.");
22230            assert_eq!(highlighted_edits.highlights.len(), 1);
22231            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
22232            assert_eq!(
22233                highlighted_edits.highlights[0].1.background_color,
22234                Some(cx.theme().status().created_background)
22235            );
22236        },
22237    )
22238    .await;
22239
22240    // Multiple edits
22241    assert_highlighted_edits(
22242        "Hello, world!",
22243        vec![
22244            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
22245            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
22246        ],
22247        false,
22248        cx,
22249        |highlighted_edits, cx| {
22250            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
22251            assert_eq!(highlighted_edits.highlights.len(), 2);
22252            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
22253            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
22254            assert_eq!(
22255                highlighted_edits.highlights[0].1.background_color,
22256                Some(cx.theme().status().created_background)
22257            );
22258            assert_eq!(
22259                highlighted_edits.highlights[1].1.background_color,
22260                Some(cx.theme().status().created_background)
22261            );
22262        },
22263    )
22264    .await;
22265
22266    // Multiple lines with edits
22267    assert_highlighted_edits(
22268        "First line\nSecond line\nThird line\nFourth line",
22269        vec![
22270            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
22271            (
22272                Point::new(2, 0)..Point::new(2, 10),
22273                "New third line".to_string(),
22274            ),
22275            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
22276        ],
22277        false,
22278        cx,
22279        |highlighted_edits, cx| {
22280            assert_eq!(
22281                highlighted_edits.text,
22282                "Second modified\nNew third line\nFourth updated line"
22283            );
22284            assert_eq!(highlighted_edits.highlights.len(), 3);
22285            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
22286            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
22287            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
22288            for highlight in &highlighted_edits.highlights {
22289                assert_eq!(
22290                    highlight.1.background_color,
22291                    Some(cx.theme().status().created_background)
22292                );
22293            }
22294        },
22295    )
22296    .await;
22297}
22298
22299#[gpui::test]
22300async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
22301    init_test(cx, |_| {});
22302
22303    // Deletion
22304    assert_highlighted_edits(
22305        "Hello, world!",
22306        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
22307        true,
22308        cx,
22309        |highlighted_edits, cx| {
22310            assert_eq!(highlighted_edits.text, "Hello, world!");
22311            assert_eq!(highlighted_edits.highlights.len(), 1);
22312            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
22313            assert_eq!(
22314                highlighted_edits.highlights[0].1.background_color,
22315                Some(cx.theme().status().deleted_background)
22316            );
22317        },
22318    )
22319    .await;
22320
22321    // Insertion
22322    assert_highlighted_edits(
22323        "Hello, world!",
22324        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
22325        true,
22326        cx,
22327        |highlighted_edits, cx| {
22328            assert_eq!(highlighted_edits.highlights.len(), 1);
22329            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
22330            assert_eq!(
22331                highlighted_edits.highlights[0].1.background_color,
22332                Some(cx.theme().status().created_background)
22333            );
22334        },
22335    )
22336    .await;
22337}
22338
22339async fn assert_highlighted_edits(
22340    text: &str,
22341    edits: Vec<(Range<Point>, String)>,
22342    include_deletions: bool,
22343    cx: &mut TestAppContext,
22344    assertion_fn: impl Fn(HighlightedText, &App),
22345) {
22346    let window = cx.add_window(|window, cx| {
22347        let buffer = MultiBuffer::build_simple(text, cx);
22348        Editor::new(EditorMode::full(), buffer, None, window, cx)
22349    });
22350    let cx = &mut VisualTestContext::from_window(*window, cx);
22351
22352    let (buffer, snapshot) = window
22353        .update(cx, |editor, _window, cx| {
22354            (
22355                editor.buffer().clone(),
22356                editor.buffer().read(cx).snapshot(cx),
22357            )
22358        })
22359        .unwrap();
22360
22361    let edits = edits
22362        .into_iter()
22363        .map(|(range, edit)| {
22364            (
22365                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
22366                edit,
22367            )
22368        })
22369        .collect::<Vec<_>>();
22370
22371    let text_anchor_edits = edits
22372        .clone()
22373        .into_iter()
22374        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
22375        .collect::<Vec<_>>();
22376
22377    let edit_preview = window
22378        .update(cx, |_, _window, cx| {
22379            buffer
22380                .read(cx)
22381                .as_singleton()
22382                .unwrap()
22383                .read(cx)
22384                .preview_edits(text_anchor_edits.into(), cx)
22385        })
22386        .unwrap()
22387        .await;
22388
22389    cx.update(|_window, cx| {
22390        let highlighted_edits = edit_prediction_edit_text(
22391            snapshot.as_singleton().unwrap().2,
22392            &edits,
22393            &edit_preview,
22394            include_deletions,
22395            cx,
22396        );
22397        assertion_fn(highlighted_edits, cx)
22398    });
22399}
22400
22401#[track_caller]
22402fn assert_breakpoint(
22403    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
22404    path: &Arc<Path>,
22405    expected: Vec<(u32, Breakpoint)>,
22406) {
22407    if expected.is_empty() {
22408        assert!(!breakpoints.contains_key(path), "{}", path.display());
22409    } else {
22410        let mut breakpoint = breakpoints
22411            .get(path)
22412            .unwrap()
22413            .iter()
22414            .map(|breakpoint| {
22415                (
22416                    breakpoint.row,
22417                    Breakpoint {
22418                        message: breakpoint.message.clone(),
22419                        state: breakpoint.state,
22420                        condition: breakpoint.condition.clone(),
22421                        hit_condition: breakpoint.hit_condition.clone(),
22422                    },
22423                )
22424            })
22425            .collect::<Vec<_>>();
22426
22427        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
22428
22429        assert_eq!(expected, breakpoint);
22430    }
22431}
22432
22433fn add_log_breakpoint_at_cursor(
22434    editor: &mut Editor,
22435    log_message: &str,
22436    window: &mut Window,
22437    cx: &mut Context<Editor>,
22438) {
22439    let (anchor, bp) = editor
22440        .breakpoints_at_cursors(window, cx)
22441        .first()
22442        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
22443        .unwrap_or_else(|| {
22444            let cursor_position: Point = editor.selections.newest(cx).head();
22445
22446            let breakpoint_position = editor
22447                .snapshot(window, cx)
22448                .display_snapshot
22449                .buffer_snapshot
22450                .anchor_before(Point::new(cursor_position.row, 0));
22451
22452            (breakpoint_position, Breakpoint::new_log(log_message))
22453        });
22454
22455    editor.edit_breakpoint_at_anchor(
22456        anchor,
22457        bp,
22458        BreakpointEditAction::EditLogMessage(log_message.into()),
22459        cx,
22460    );
22461}
22462
22463#[gpui::test]
22464async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
22465    init_test(cx, |_| {});
22466
22467    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22468    let fs = FakeFs::new(cx.executor());
22469    fs.insert_tree(
22470        path!("/a"),
22471        json!({
22472            "main.rs": sample_text,
22473        }),
22474    )
22475    .await;
22476    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22477    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22478    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
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_id = workspace
22492        .update(cx, |workspace, _window, cx| {
22493            workspace.project().update(cx, |project, cx| {
22494                project.worktrees(cx).next().unwrap().read(cx).id()
22495            })
22496        })
22497        .unwrap();
22498
22499    let buffer = project
22500        .update(cx, |project, cx| {
22501            project.open_buffer((worktree_id, "main.rs"), cx)
22502        })
22503        .await
22504        .unwrap();
22505
22506    let (editor, cx) = cx.add_window_view(|window, cx| {
22507        Editor::new(
22508            EditorMode::full(),
22509            MultiBuffer::build_from_buffer(buffer, cx),
22510            Some(project.clone()),
22511            window,
22512            cx,
22513        )
22514    });
22515
22516    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22517    let abs_path = project.read_with(cx, |project, cx| {
22518        project
22519            .absolute_path(&project_path, cx)
22520            .map(Arc::from)
22521            .unwrap()
22522    });
22523
22524    // assert we can add breakpoint on the first line
22525    editor.update_in(cx, |editor, window, cx| {
22526        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22527        editor.move_to_end(&MoveToEnd, window, cx);
22528        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22529    });
22530
22531    let breakpoints = editor.update(cx, |editor, cx| {
22532        editor
22533            .breakpoint_store()
22534            .as_ref()
22535            .unwrap()
22536            .read(cx)
22537            .all_source_breakpoints(cx)
22538    });
22539
22540    assert_eq!(1, breakpoints.len());
22541    assert_breakpoint(
22542        &breakpoints,
22543        &abs_path,
22544        vec![
22545            (0, Breakpoint::new_standard()),
22546            (3, Breakpoint::new_standard()),
22547        ],
22548    );
22549
22550    editor.update_in(cx, |editor, window, cx| {
22551        editor.move_to_beginning(&MoveToBeginning, window, cx);
22552        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22553    });
22554
22555    let breakpoints = editor.update(cx, |editor, cx| {
22556        editor
22557            .breakpoint_store()
22558            .as_ref()
22559            .unwrap()
22560            .read(cx)
22561            .all_source_breakpoints(cx)
22562    });
22563
22564    assert_eq!(1, breakpoints.len());
22565    assert_breakpoint(
22566        &breakpoints,
22567        &abs_path,
22568        vec![(3, Breakpoint::new_standard())],
22569    );
22570
22571    editor.update_in(cx, |editor, window, cx| {
22572        editor.move_to_end(&MoveToEnd, window, cx);
22573        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22574    });
22575
22576    let breakpoints = editor.update(cx, |editor, cx| {
22577        editor
22578            .breakpoint_store()
22579            .as_ref()
22580            .unwrap()
22581            .read(cx)
22582            .all_source_breakpoints(cx)
22583    });
22584
22585    assert_eq!(0, breakpoints.len());
22586    assert_breakpoint(&breakpoints, &abs_path, vec![]);
22587}
22588
22589#[gpui::test]
22590async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
22591    init_test(cx, |_| {});
22592
22593    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22594
22595    let fs = FakeFs::new(cx.executor());
22596    fs.insert_tree(
22597        path!("/a"),
22598        json!({
22599            "main.rs": sample_text,
22600        }),
22601    )
22602    .await;
22603    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22604    let (workspace, cx) =
22605        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22606
22607    let worktree_id = workspace.update(cx, |workspace, cx| {
22608        workspace.project().update(cx, |project, cx| {
22609            project.worktrees(cx).next().unwrap().read(cx).id()
22610        })
22611    });
22612
22613    let buffer = project
22614        .update(cx, |project, cx| {
22615            project.open_buffer((worktree_id, "main.rs"), cx)
22616        })
22617        .await
22618        .unwrap();
22619
22620    let (editor, cx) = cx.add_window_view(|window, cx| {
22621        Editor::new(
22622            EditorMode::full(),
22623            MultiBuffer::build_from_buffer(buffer, cx),
22624            Some(project.clone()),
22625            window,
22626            cx,
22627        )
22628    });
22629
22630    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22631    let abs_path = project.read_with(cx, |project, cx| {
22632        project
22633            .absolute_path(&project_path, cx)
22634            .map(Arc::from)
22635            .unwrap()
22636    });
22637
22638    editor.update_in(cx, |editor, window, cx| {
22639        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
22640    });
22641
22642    let breakpoints = editor.update(cx, |editor, cx| {
22643        editor
22644            .breakpoint_store()
22645            .as_ref()
22646            .unwrap()
22647            .read(cx)
22648            .all_source_breakpoints(cx)
22649    });
22650
22651    assert_breakpoint(
22652        &breakpoints,
22653        &abs_path,
22654        vec![(0, Breakpoint::new_log("hello world"))],
22655    );
22656
22657    // Removing a log message from a log breakpoint should remove it
22658    editor.update_in(cx, |editor, window, cx| {
22659        add_log_breakpoint_at_cursor(editor, "", window, cx);
22660    });
22661
22662    let breakpoints = editor.update(cx, |editor, cx| {
22663        editor
22664            .breakpoint_store()
22665            .as_ref()
22666            .unwrap()
22667            .read(cx)
22668            .all_source_breakpoints(cx)
22669    });
22670
22671    assert_breakpoint(&breakpoints, &abs_path, vec![]);
22672
22673    editor.update_in(cx, |editor, window, cx| {
22674        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22675        editor.move_to_end(&MoveToEnd, window, cx);
22676        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22677        // Not adding a log message to a standard breakpoint shouldn't remove it
22678        add_log_breakpoint_at_cursor(editor, "", window, cx);
22679    });
22680
22681    let breakpoints = editor.update(cx, |editor, cx| {
22682        editor
22683            .breakpoint_store()
22684            .as_ref()
22685            .unwrap()
22686            .read(cx)
22687            .all_source_breakpoints(cx)
22688    });
22689
22690    assert_breakpoint(
22691        &breakpoints,
22692        &abs_path,
22693        vec![
22694            (0, Breakpoint::new_standard()),
22695            (3, Breakpoint::new_standard()),
22696        ],
22697    );
22698
22699    editor.update_in(cx, |editor, window, cx| {
22700        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
22701    });
22702
22703    let breakpoints = editor.update(cx, |editor, cx| {
22704        editor
22705            .breakpoint_store()
22706            .as_ref()
22707            .unwrap()
22708            .read(cx)
22709            .all_source_breakpoints(cx)
22710    });
22711
22712    assert_breakpoint(
22713        &breakpoints,
22714        &abs_path,
22715        vec![
22716            (0, Breakpoint::new_standard()),
22717            (3, Breakpoint::new_log("hello world")),
22718        ],
22719    );
22720
22721    editor.update_in(cx, |editor, window, cx| {
22722        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
22723    });
22724
22725    let breakpoints = editor.update(cx, |editor, cx| {
22726        editor
22727            .breakpoint_store()
22728            .as_ref()
22729            .unwrap()
22730            .read(cx)
22731            .all_source_breakpoints(cx)
22732    });
22733
22734    assert_breakpoint(
22735        &breakpoints,
22736        &abs_path,
22737        vec![
22738            (0, Breakpoint::new_standard()),
22739            (3, Breakpoint::new_log("hello Earth!!")),
22740        ],
22741    );
22742}
22743
22744/// This also tests that Editor::breakpoint_at_cursor_head is working properly
22745/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
22746/// or when breakpoints were placed out of order. This tests for a regression too
22747#[gpui::test]
22748async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
22749    init_test(cx, |_| {});
22750
22751    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22752    let fs = FakeFs::new(cx.executor());
22753    fs.insert_tree(
22754        path!("/a"),
22755        json!({
22756            "main.rs": sample_text,
22757        }),
22758    )
22759    .await;
22760    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22761    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22762    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22763
22764    let fs = FakeFs::new(cx.executor());
22765    fs.insert_tree(
22766        path!("/a"),
22767        json!({
22768            "main.rs": sample_text,
22769        }),
22770    )
22771    .await;
22772    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22773    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22774    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22775    let worktree_id = workspace
22776        .update(cx, |workspace, _window, cx| {
22777            workspace.project().update(cx, |project, cx| {
22778                project.worktrees(cx).next().unwrap().read(cx).id()
22779            })
22780        })
22781        .unwrap();
22782
22783    let buffer = project
22784        .update(cx, |project, cx| {
22785            project.open_buffer((worktree_id, "main.rs"), cx)
22786        })
22787        .await
22788        .unwrap();
22789
22790    let (editor, cx) = cx.add_window_view(|window, cx| {
22791        Editor::new(
22792            EditorMode::full(),
22793            MultiBuffer::build_from_buffer(buffer, cx),
22794            Some(project.clone()),
22795            window,
22796            cx,
22797        )
22798    });
22799
22800    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22801    let abs_path = project.read_with(cx, |project, cx| {
22802        project
22803            .absolute_path(&project_path, cx)
22804            .map(Arc::from)
22805            .unwrap()
22806    });
22807
22808    // assert we can add breakpoint on the first line
22809    editor.update_in(cx, |editor, window, cx| {
22810        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22811        editor.move_to_end(&MoveToEnd, window, cx);
22812        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22813        editor.move_up(&MoveUp, window, cx);
22814        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22815    });
22816
22817    let breakpoints = editor.update(cx, |editor, cx| {
22818        editor
22819            .breakpoint_store()
22820            .as_ref()
22821            .unwrap()
22822            .read(cx)
22823            .all_source_breakpoints(cx)
22824    });
22825
22826    assert_eq!(1, breakpoints.len());
22827    assert_breakpoint(
22828        &breakpoints,
22829        &abs_path,
22830        vec![
22831            (0, Breakpoint::new_standard()),
22832            (2, Breakpoint::new_standard()),
22833            (3, Breakpoint::new_standard()),
22834        ],
22835    );
22836
22837    editor.update_in(cx, |editor, window, cx| {
22838        editor.move_to_beginning(&MoveToBeginning, window, cx);
22839        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22840        editor.move_to_end(&MoveToEnd, window, cx);
22841        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22842        // Disabling a breakpoint that doesn't exist should do nothing
22843        editor.move_up(&MoveUp, window, cx);
22844        editor.move_up(&MoveUp, window, cx);
22845        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22846    });
22847
22848    let breakpoints = editor.update(cx, |editor, cx| {
22849        editor
22850            .breakpoint_store()
22851            .as_ref()
22852            .unwrap()
22853            .read(cx)
22854            .all_source_breakpoints(cx)
22855    });
22856
22857    let disable_breakpoint = {
22858        let mut bp = Breakpoint::new_standard();
22859        bp.state = BreakpointState::Disabled;
22860        bp
22861    };
22862
22863    assert_eq!(1, breakpoints.len());
22864    assert_breakpoint(
22865        &breakpoints,
22866        &abs_path,
22867        vec![
22868            (0, disable_breakpoint.clone()),
22869            (2, Breakpoint::new_standard()),
22870            (3, disable_breakpoint.clone()),
22871        ],
22872    );
22873
22874    editor.update_in(cx, |editor, window, cx| {
22875        editor.move_to_beginning(&MoveToBeginning, window, cx);
22876        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
22877        editor.move_to_end(&MoveToEnd, window, cx);
22878        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
22879        editor.move_up(&MoveUp, window, cx);
22880        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22881    });
22882
22883    let breakpoints = editor.update(cx, |editor, cx| {
22884        editor
22885            .breakpoint_store()
22886            .as_ref()
22887            .unwrap()
22888            .read(cx)
22889            .all_source_breakpoints(cx)
22890    });
22891
22892    assert_eq!(1, breakpoints.len());
22893    assert_breakpoint(
22894        &breakpoints,
22895        &abs_path,
22896        vec![
22897            (0, Breakpoint::new_standard()),
22898            (2, disable_breakpoint),
22899            (3, Breakpoint::new_standard()),
22900        ],
22901    );
22902}
22903
22904#[gpui::test]
22905async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
22906    init_test(cx, |_| {});
22907    let capabilities = lsp::ServerCapabilities {
22908        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
22909            prepare_provider: Some(true),
22910            work_done_progress_options: Default::default(),
22911        })),
22912        ..Default::default()
22913    };
22914    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
22915
22916    cx.set_state(indoc! {"
22917        struct Fˇoo {}
22918    "});
22919
22920    cx.update_editor(|editor, _, cx| {
22921        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
22922        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
22923        editor.highlight_background::<DocumentHighlightRead>(
22924            &[highlight_range],
22925            |theme| theme.colors().editor_document_highlight_read_background,
22926            cx,
22927        );
22928    });
22929
22930    let mut prepare_rename_handler = cx
22931        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
22932            move |_, _, _| async move {
22933                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
22934                    start: lsp::Position {
22935                        line: 0,
22936                        character: 7,
22937                    },
22938                    end: lsp::Position {
22939                        line: 0,
22940                        character: 10,
22941                    },
22942                })))
22943            },
22944        );
22945    let prepare_rename_task = cx
22946        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
22947        .expect("Prepare rename was not started");
22948    prepare_rename_handler.next().await.unwrap();
22949    prepare_rename_task.await.expect("Prepare rename failed");
22950
22951    let mut rename_handler =
22952        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
22953            let edit = lsp::TextEdit {
22954                range: lsp::Range {
22955                    start: lsp::Position {
22956                        line: 0,
22957                        character: 7,
22958                    },
22959                    end: lsp::Position {
22960                        line: 0,
22961                        character: 10,
22962                    },
22963                },
22964                new_text: "FooRenamed".to_string(),
22965            };
22966            Ok(Some(lsp::WorkspaceEdit::new(
22967                // Specify the same edit twice
22968                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
22969            )))
22970        });
22971    let rename_task = cx
22972        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
22973        .expect("Confirm rename was not started");
22974    rename_handler.next().await.unwrap();
22975    rename_task.await.expect("Confirm rename failed");
22976    cx.run_until_parked();
22977
22978    // Despite two edits, only one is actually applied as those are identical
22979    cx.assert_editor_state(indoc! {"
22980        struct FooRenamedˇ {}
22981    "});
22982}
22983
22984#[gpui::test]
22985async fn test_rename_without_prepare(cx: &mut TestAppContext) {
22986    init_test(cx, |_| {});
22987    // These capabilities indicate that the server does not support prepare rename.
22988    let capabilities = lsp::ServerCapabilities {
22989        rename_provider: Some(lsp::OneOf::Left(true)),
22990        ..Default::default()
22991    };
22992    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
22993
22994    cx.set_state(indoc! {"
22995        struct Fˇoo {}
22996    "});
22997
22998    cx.update_editor(|editor, _window, cx| {
22999        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
23000        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
23001        editor.highlight_background::<DocumentHighlightRead>(
23002            &[highlight_range],
23003            |theme| theme.colors().editor_document_highlight_read_background,
23004            cx,
23005        );
23006    });
23007
23008    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23009        .expect("Prepare rename was not started")
23010        .await
23011        .expect("Prepare rename failed");
23012
23013    let mut rename_handler =
23014        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23015            let edit = lsp::TextEdit {
23016                range: lsp::Range {
23017                    start: lsp::Position {
23018                        line: 0,
23019                        character: 7,
23020                    },
23021                    end: lsp::Position {
23022                        line: 0,
23023                        character: 10,
23024                    },
23025                },
23026                new_text: "FooRenamed".to_string(),
23027            };
23028            Ok(Some(lsp::WorkspaceEdit::new(
23029                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
23030            )))
23031        });
23032    let rename_task = cx
23033        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23034        .expect("Confirm rename was not started");
23035    rename_handler.next().await.unwrap();
23036    rename_task.await.expect("Confirm rename failed");
23037    cx.run_until_parked();
23038
23039    // Correct range is renamed, as `surrounding_word` is used to find it.
23040    cx.assert_editor_state(indoc! {"
23041        struct FooRenamedˇ {}
23042    "});
23043}
23044
23045#[gpui::test]
23046async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
23047    init_test(cx, |_| {});
23048    let mut cx = EditorTestContext::new(cx).await;
23049
23050    let language = Arc::new(
23051        Language::new(
23052            LanguageConfig::default(),
23053            Some(tree_sitter_html::LANGUAGE.into()),
23054        )
23055        .with_brackets_query(
23056            r#"
23057            ("<" @open "/>" @close)
23058            ("</" @open ">" @close)
23059            ("<" @open ">" @close)
23060            ("\"" @open "\"" @close)
23061            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
23062        "#,
23063        )
23064        .unwrap(),
23065    );
23066    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23067
23068    cx.set_state(indoc! {"
23069        <span>ˇ</span>
23070    "});
23071    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23072    cx.assert_editor_state(indoc! {"
23073        <span>
23074        ˇ
23075        </span>
23076    "});
23077
23078    cx.set_state(indoc! {"
23079        <span><span></span>ˇ</span>
23080    "});
23081    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23082    cx.assert_editor_state(indoc! {"
23083        <span><span></span>
23084        ˇ</span>
23085    "});
23086
23087    cx.set_state(indoc! {"
23088        <span>ˇ
23089        </span>
23090    "});
23091    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23092    cx.assert_editor_state(indoc! {"
23093        <span>
23094        ˇ
23095        </span>
23096    "});
23097}
23098
23099#[gpui::test(iterations = 10)]
23100async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
23101    init_test(cx, |_| {});
23102
23103    let fs = FakeFs::new(cx.executor());
23104    fs.insert_tree(
23105        path!("/dir"),
23106        json!({
23107            "a.ts": "a",
23108        }),
23109    )
23110    .await;
23111
23112    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
23113    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23114    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23115
23116    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23117    language_registry.add(Arc::new(Language::new(
23118        LanguageConfig {
23119            name: "TypeScript".into(),
23120            matcher: LanguageMatcher {
23121                path_suffixes: vec!["ts".to_string()],
23122                ..Default::default()
23123            },
23124            ..Default::default()
23125        },
23126        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
23127    )));
23128    let mut fake_language_servers = language_registry.register_fake_lsp(
23129        "TypeScript",
23130        FakeLspAdapter {
23131            capabilities: lsp::ServerCapabilities {
23132                code_lens_provider: Some(lsp::CodeLensOptions {
23133                    resolve_provider: Some(true),
23134                }),
23135                execute_command_provider: Some(lsp::ExecuteCommandOptions {
23136                    commands: vec!["_the/command".to_string()],
23137                    ..lsp::ExecuteCommandOptions::default()
23138                }),
23139                ..lsp::ServerCapabilities::default()
23140            },
23141            ..FakeLspAdapter::default()
23142        },
23143    );
23144
23145    let editor = workspace
23146        .update(cx, |workspace, window, cx| {
23147            workspace.open_abs_path(
23148                PathBuf::from(path!("/dir/a.ts")),
23149                OpenOptions::default(),
23150                window,
23151                cx,
23152            )
23153        })
23154        .unwrap()
23155        .await
23156        .unwrap()
23157        .downcast::<Editor>()
23158        .unwrap();
23159    cx.executor().run_until_parked();
23160
23161    let fake_server = fake_language_servers.next().await.unwrap();
23162
23163    let buffer = editor.update(cx, |editor, cx| {
23164        editor
23165            .buffer()
23166            .read(cx)
23167            .as_singleton()
23168            .expect("have opened a single file by path")
23169    });
23170
23171    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
23172    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
23173    drop(buffer_snapshot);
23174    let actions = cx
23175        .update_window(*workspace, |_, window, cx| {
23176            project.code_actions(&buffer, anchor..anchor, window, cx)
23177        })
23178        .unwrap();
23179
23180    fake_server
23181        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23182            Ok(Some(vec![
23183                lsp::CodeLens {
23184                    range: lsp::Range::default(),
23185                    command: Some(lsp::Command {
23186                        title: "Code lens command".to_owned(),
23187                        command: "_the/command".to_owned(),
23188                        arguments: None,
23189                    }),
23190                    data: None,
23191                },
23192                lsp::CodeLens {
23193                    range: lsp::Range::default(),
23194                    command: Some(lsp::Command {
23195                        title: "Command not in capabilities".to_owned(),
23196                        command: "not in capabilities".to_owned(),
23197                        arguments: None,
23198                    }),
23199                    data: None,
23200                },
23201                lsp::CodeLens {
23202                    range: lsp::Range {
23203                        start: lsp::Position {
23204                            line: 1,
23205                            character: 1,
23206                        },
23207                        end: lsp::Position {
23208                            line: 1,
23209                            character: 1,
23210                        },
23211                    },
23212                    command: Some(lsp::Command {
23213                        title: "Command not in range".to_owned(),
23214                        command: "_the/command".to_owned(),
23215                        arguments: None,
23216                    }),
23217                    data: None,
23218                },
23219            ]))
23220        })
23221        .next()
23222        .await;
23223
23224    let actions = actions.await.unwrap();
23225    assert_eq!(
23226        actions.len(),
23227        1,
23228        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
23229    );
23230    let action = actions[0].clone();
23231    let apply = project.update(cx, |project, cx| {
23232        project.apply_code_action(buffer.clone(), action, true, cx)
23233    });
23234
23235    // Resolving the code action does not populate its edits. In absence of
23236    // edits, we must execute the given command.
23237    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
23238        |mut lens, _| async move {
23239            let lens_command = lens.command.as_mut().expect("should have a command");
23240            assert_eq!(lens_command.title, "Code lens command");
23241            lens_command.arguments = Some(vec![json!("the-argument")]);
23242            Ok(lens)
23243        },
23244    );
23245
23246    // While executing the command, the language server sends the editor
23247    // a `workspaceEdit` request.
23248    fake_server
23249        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
23250            let fake = fake_server.clone();
23251            move |params, _| {
23252                assert_eq!(params.command, "_the/command");
23253                let fake = fake.clone();
23254                async move {
23255                    fake.server
23256                        .request::<lsp::request::ApplyWorkspaceEdit>(
23257                            lsp::ApplyWorkspaceEditParams {
23258                                label: None,
23259                                edit: lsp::WorkspaceEdit {
23260                                    changes: Some(
23261                                        [(
23262                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
23263                                            vec![lsp::TextEdit {
23264                                                range: lsp::Range::new(
23265                                                    lsp::Position::new(0, 0),
23266                                                    lsp::Position::new(0, 0),
23267                                                ),
23268                                                new_text: "X".into(),
23269                                            }],
23270                                        )]
23271                                        .into_iter()
23272                                        .collect(),
23273                                    ),
23274                                    ..lsp::WorkspaceEdit::default()
23275                                },
23276                            },
23277                        )
23278                        .await
23279                        .into_response()
23280                        .unwrap();
23281                    Ok(Some(json!(null)))
23282                }
23283            }
23284        })
23285        .next()
23286        .await;
23287
23288    // Applying the code lens command returns a project transaction containing the edits
23289    // sent by the language server in its `workspaceEdit` request.
23290    let transaction = apply.await.unwrap();
23291    assert!(transaction.0.contains_key(&buffer));
23292    buffer.update(cx, |buffer, cx| {
23293        assert_eq!(buffer.text(), "Xa");
23294        buffer.undo(cx);
23295        assert_eq!(buffer.text(), "a");
23296    });
23297
23298    let actions_after_edits = cx
23299        .update_window(*workspace, |_, window, cx| {
23300            project.code_actions(&buffer, anchor..anchor, window, cx)
23301        })
23302        .unwrap()
23303        .await
23304        .unwrap();
23305    assert_eq!(
23306        actions, actions_after_edits,
23307        "For the same selection, same code lens actions should be returned"
23308    );
23309
23310    let _responses =
23311        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23312            panic!("No more code lens requests are expected");
23313        });
23314    editor.update_in(cx, |editor, window, cx| {
23315        editor.select_all(&SelectAll, window, cx);
23316    });
23317    cx.executor().run_until_parked();
23318    let new_actions = cx
23319        .update_window(*workspace, |_, window, cx| {
23320            project.code_actions(&buffer, anchor..anchor, window, cx)
23321        })
23322        .unwrap()
23323        .await
23324        .unwrap();
23325    assert_eq!(
23326        actions, new_actions,
23327        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
23328    );
23329}
23330
23331#[gpui::test]
23332async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
23333    init_test(cx, |_| {});
23334
23335    let fs = FakeFs::new(cx.executor());
23336    let main_text = r#"fn main() {
23337println!("1");
23338println!("2");
23339println!("3");
23340println!("4");
23341println!("5");
23342}"#;
23343    let lib_text = "mod foo {}";
23344    fs.insert_tree(
23345        path!("/a"),
23346        json!({
23347            "lib.rs": lib_text,
23348            "main.rs": main_text,
23349        }),
23350    )
23351    .await;
23352
23353    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23354    let (workspace, cx) =
23355        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23356    let worktree_id = workspace.update(cx, |workspace, cx| {
23357        workspace.project().update(cx, |project, cx| {
23358            project.worktrees(cx).next().unwrap().read(cx).id()
23359        })
23360    });
23361
23362    let expected_ranges = vec![
23363        Point::new(0, 0)..Point::new(0, 0),
23364        Point::new(1, 0)..Point::new(1, 1),
23365        Point::new(2, 0)..Point::new(2, 2),
23366        Point::new(3, 0)..Point::new(3, 3),
23367    ];
23368
23369    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23370    let editor_1 = workspace
23371        .update_in(cx, |workspace, window, cx| {
23372            workspace.open_path(
23373                (worktree_id, "main.rs"),
23374                Some(pane_1.downgrade()),
23375                true,
23376                window,
23377                cx,
23378            )
23379        })
23380        .unwrap()
23381        .await
23382        .downcast::<Editor>()
23383        .unwrap();
23384    pane_1.update(cx, |pane, cx| {
23385        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23386        open_editor.update(cx, |editor, cx| {
23387            assert_eq!(
23388                editor.display_text(cx),
23389                main_text,
23390                "Original main.rs text on initial open",
23391            );
23392            assert_eq!(
23393                editor
23394                    .selections
23395                    .all::<Point>(cx)
23396                    .into_iter()
23397                    .map(|s| s.range())
23398                    .collect::<Vec<_>>(),
23399                vec![Point::zero()..Point::zero()],
23400                "Default selections on initial open",
23401            );
23402        })
23403    });
23404    editor_1.update_in(cx, |editor, window, cx| {
23405        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23406            s.select_ranges(expected_ranges.clone());
23407        });
23408    });
23409
23410    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
23411        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
23412    });
23413    let editor_2 = workspace
23414        .update_in(cx, |workspace, window, cx| {
23415            workspace.open_path(
23416                (worktree_id, "main.rs"),
23417                Some(pane_2.downgrade()),
23418                true,
23419                window,
23420                cx,
23421            )
23422        })
23423        .unwrap()
23424        .await
23425        .downcast::<Editor>()
23426        .unwrap();
23427    pane_2.update(cx, |pane, cx| {
23428        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23429        open_editor.update(cx, |editor, cx| {
23430            assert_eq!(
23431                editor.display_text(cx),
23432                main_text,
23433                "Original main.rs text on initial open in another panel",
23434            );
23435            assert_eq!(
23436                editor
23437                    .selections
23438                    .all::<Point>(cx)
23439                    .into_iter()
23440                    .map(|s| s.range())
23441                    .collect::<Vec<_>>(),
23442                vec![Point::zero()..Point::zero()],
23443                "Default selections on initial open in another panel",
23444            );
23445        })
23446    });
23447
23448    editor_2.update_in(cx, |editor, window, cx| {
23449        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
23450    });
23451
23452    let _other_editor_1 = workspace
23453        .update_in(cx, |workspace, window, cx| {
23454            workspace.open_path(
23455                (worktree_id, "lib.rs"),
23456                Some(pane_1.downgrade()),
23457                true,
23458                window,
23459                cx,
23460            )
23461        })
23462        .unwrap()
23463        .await
23464        .downcast::<Editor>()
23465        .unwrap();
23466    pane_1
23467        .update_in(cx, |pane, window, cx| {
23468            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23469        })
23470        .await
23471        .unwrap();
23472    drop(editor_1);
23473    pane_1.update(cx, |pane, cx| {
23474        pane.active_item()
23475            .unwrap()
23476            .downcast::<Editor>()
23477            .unwrap()
23478            .update(cx, |editor, cx| {
23479                assert_eq!(
23480                    editor.display_text(cx),
23481                    lib_text,
23482                    "Other file should be open and active",
23483                );
23484            });
23485        assert_eq!(pane.items().count(), 1, "No other editors should be open");
23486    });
23487
23488    let _other_editor_2 = workspace
23489        .update_in(cx, |workspace, window, cx| {
23490            workspace.open_path(
23491                (worktree_id, "lib.rs"),
23492                Some(pane_2.downgrade()),
23493                true,
23494                window,
23495                cx,
23496            )
23497        })
23498        .unwrap()
23499        .await
23500        .downcast::<Editor>()
23501        .unwrap();
23502    pane_2
23503        .update_in(cx, |pane, window, cx| {
23504            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23505        })
23506        .await
23507        .unwrap();
23508    drop(editor_2);
23509    pane_2.update(cx, |pane, cx| {
23510        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23511        open_editor.update(cx, |editor, cx| {
23512            assert_eq!(
23513                editor.display_text(cx),
23514                lib_text,
23515                "Other file should be open and active in another panel too",
23516            );
23517        });
23518        assert_eq!(
23519            pane.items().count(),
23520            1,
23521            "No other editors should be open in another pane",
23522        );
23523    });
23524
23525    let _editor_1_reopened = workspace
23526        .update_in(cx, |workspace, window, cx| {
23527            workspace.open_path(
23528                (worktree_id, "main.rs"),
23529                Some(pane_1.downgrade()),
23530                true,
23531                window,
23532                cx,
23533            )
23534        })
23535        .unwrap()
23536        .await
23537        .downcast::<Editor>()
23538        .unwrap();
23539    let _editor_2_reopened = workspace
23540        .update_in(cx, |workspace, window, cx| {
23541            workspace.open_path(
23542                (worktree_id, "main.rs"),
23543                Some(pane_2.downgrade()),
23544                true,
23545                window,
23546                cx,
23547            )
23548        })
23549        .unwrap()
23550        .await
23551        .downcast::<Editor>()
23552        .unwrap();
23553    pane_1.update(cx, |pane, cx| {
23554        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23555        open_editor.update(cx, |editor, cx| {
23556            assert_eq!(
23557                editor.display_text(cx),
23558                main_text,
23559                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
23560            );
23561            assert_eq!(
23562                editor
23563                    .selections
23564                    .all::<Point>(cx)
23565                    .into_iter()
23566                    .map(|s| s.range())
23567                    .collect::<Vec<_>>(),
23568                expected_ranges,
23569                "Previous editor in the 1st panel had selections and should get them restored on reopen",
23570            );
23571        })
23572    });
23573    pane_2.update(cx, |pane, cx| {
23574        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23575        open_editor.update(cx, |editor, cx| {
23576            assert_eq!(
23577                editor.display_text(cx),
23578                r#"fn main() {
23579⋯rintln!("1");
23580⋯intln!("2");
23581⋯ntln!("3");
23582println!("4");
23583println!("5");
23584}"#,
23585                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
23586            );
23587            assert_eq!(
23588                editor
23589                    .selections
23590                    .all::<Point>(cx)
23591                    .into_iter()
23592                    .map(|s| s.range())
23593                    .collect::<Vec<_>>(),
23594                vec![Point::zero()..Point::zero()],
23595                "Previous editor in the 2nd pane had no selections changed hence should restore none",
23596            );
23597        })
23598    });
23599}
23600
23601#[gpui::test]
23602async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
23603    init_test(cx, |_| {});
23604
23605    let fs = FakeFs::new(cx.executor());
23606    let main_text = r#"fn main() {
23607println!("1");
23608println!("2");
23609println!("3");
23610println!("4");
23611println!("5");
23612}"#;
23613    let lib_text = "mod foo {}";
23614    fs.insert_tree(
23615        path!("/a"),
23616        json!({
23617            "lib.rs": lib_text,
23618            "main.rs": main_text,
23619        }),
23620    )
23621    .await;
23622
23623    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23624    let (workspace, cx) =
23625        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23626    let worktree_id = workspace.update(cx, |workspace, cx| {
23627        workspace.project().update(cx, |project, cx| {
23628            project.worktrees(cx).next().unwrap().read(cx).id()
23629        })
23630    });
23631
23632    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23633    let editor = workspace
23634        .update_in(cx, |workspace, window, cx| {
23635            workspace.open_path(
23636                (worktree_id, "main.rs"),
23637                Some(pane.downgrade()),
23638                true,
23639                window,
23640                cx,
23641            )
23642        })
23643        .unwrap()
23644        .await
23645        .downcast::<Editor>()
23646        .unwrap();
23647    pane.update(cx, |pane, cx| {
23648        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23649        open_editor.update(cx, |editor, cx| {
23650            assert_eq!(
23651                editor.display_text(cx),
23652                main_text,
23653                "Original main.rs text on initial open",
23654            );
23655        })
23656    });
23657    editor.update_in(cx, |editor, window, cx| {
23658        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
23659    });
23660
23661    cx.update_global(|store: &mut SettingsStore, cx| {
23662        store.update_user_settings(cx, |s| {
23663            s.workspace.restore_on_file_reopen = Some(false);
23664        });
23665    });
23666    editor.update_in(cx, |editor, window, cx| {
23667        editor.fold_ranges(
23668            vec![
23669                Point::new(1, 0)..Point::new(1, 1),
23670                Point::new(2, 0)..Point::new(2, 2),
23671                Point::new(3, 0)..Point::new(3, 3),
23672            ],
23673            false,
23674            window,
23675            cx,
23676        );
23677    });
23678    pane.update_in(cx, |pane, window, cx| {
23679        pane.close_all_items(&CloseAllItems::default(), window, cx)
23680    })
23681    .await
23682    .unwrap();
23683    pane.update(cx, |pane, _| {
23684        assert!(pane.active_item().is_none());
23685    });
23686    cx.update_global(|store: &mut SettingsStore, cx| {
23687        store.update_user_settings(cx, |s| {
23688            s.workspace.restore_on_file_reopen = Some(true);
23689        });
23690    });
23691
23692    let _editor_reopened = workspace
23693        .update_in(cx, |workspace, window, cx| {
23694            workspace.open_path(
23695                (worktree_id, "main.rs"),
23696                Some(pane.downgrade()),
23697                true,
23698                window,
23699                cx,
23700            )
23701        })
23702        .unwrap()
23703        .await
23704        .downcast::<Editor>()
23705        .unwrap();
23706    pane.update(cx, |pane, cx| {
23707        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23708        open_editor.update(cx, |editor, cx| {
23709            assert_eq!(
23710                editor.display_text(cx),
23711                main_text,
23712                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
23713            );
23714        })
23715    });
23716}
23717
23718#[gpui::test]
23719async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
23720    struct EmptyModalView {
23721        focus_handle: gpui::FocusHandle,
23722    }
23723    impl EventEmitter<DismissEvent> for EmptyModalView {}
23724    impl Render for EmptyModalView {
23725        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
23726            div()
23727        }
23728    }
23729    impl Focusable for EmptyModalView {
23730        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
23731            self.focus_handle.clone()
23732        }
23733    }
23734    impl workspace::ModalView for EmptyModalView {}
23735    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
23736        EmptyModalView {
23737            focus_handle: cx.focus_handle(),
23738        }
23739    }
23740
23741    init_test(cx, |_| {});
23742
23743    let fs = FakeFs::new(cx.executor());
23744    let project = Project::test(fs, [], cx).await;
23745    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23746    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
23747    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23748    let editor = cx.new_window_entity(|window, cx| {
23749        Editor::new(
23750            EditorMode::full(),
23751            buffer,
23752            Some(project.clone()),
23753            window,
23754            cx,
23755        )
23756    });
23757    workspace
23758        .update(cx, |workspace, window, cx| {
23759            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
23760        })
23761        .unwrap();
23762    editor.update_in(cx, |editor, window, cx| {
23763        editor.open_context_menu(&OpenContextMenu, window, cx);
23764        assert!(editor.mouse_context_menu.is_some());
23765    });
23766    workspace
23767        .update(cx, |workspace, window, cx| {
23768            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
23769        })
23770        .unwrap();
23771    cx.read(|cx| {
23772        assert!(editor.read(cx).mouse_context_menu.is_none());
23773    });
23774}
23775
23776#[gpui::test]
23777async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
23778    init_test(cx, |_| {});
23779
23780    let fs = FakeFs::new(cx.executor());
23781    fs.insert_file(path!("/file.html"), Default::default())
23782        .await;
23783
23784    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
23785
23786    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23787    let html_language = Arc::new(Language::new(
23788        LanguageConfig {
23789            name: "HTML".into(),
23790            matcher: LanguageMatcher {
23791                path_suffixes: vec!["html".to_string()],
23792                ..LanguageMatcher::default()
23793            },
23794            brackets: BracketPairConfig {
23795                pairs: vec![BracketPair {
23796                    start: "<".into(),
23797                    end: ">".into(),
23798                    close: true,
23799                    ..Default::default()
23800                }],
23801                ..Default::default()
23802            },
23803            ..Default::default()
23804        },
23805        Some(tree_sitter_html::LANGUAGE.into()),
23806    ));
23807    language_registry.add(html_language);
23808    let mut fake_servers = language_registry.register_fake_lsp(
23809        "HTML",
23810        FakeLspAdapter {
23811            capabilities: lsp::ServerCapabilities {
23812                completion_provider: Some(lsp::CompletionOptions {
23813                    resolve_provider: Some(true),
23814                    ..Default::default()
23815                }),
23816                ..Default::default()
23817            },
23818            ..Default::default()
23819        },
23820    );
23821
23822    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23823    let cx = &mut VisualTestContext::from_window(*workspace, cx);
23824
23825    let worktree_id = workspace
23826        .update(cx, |workspace, _window, cx| {
23827            workspace.project().update(cx, |project, cx| {
23828                project.worktrees(cx).next().unwrap().read(cx).id()
23829            })
23830        })
23831        .unwrap();
23832    project
23833        .update(cx, |project, cx| {
23834            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
23835        })
23836        .await
23837        .unwrap();
23838    let editor = workspace
23839        .update(cx, |workspace, window, cx| {
23840            workspace.open_path((worktree_id, "file.html"), None, true, window, cx)
23841        })
23842        .unwrap()
23843        .await
23844        .unwrap()
23845        .downcast::<Editor>()
23846        .unwrap();
23847
23848    let fake_server = fake_servers.next().await.unwrap();
23849    editor.update_in(cx, |editor, window, cx| {
23850        editor.set_text("<ad></ad>", window, cx);
23851        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23852            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
23853        });
23854        let Some((buffer, _)) = editor
23855            .buffer
23856            .read(cx)
23857            .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
23858        else {
23859            panic!("Failed to get buffer for selection position");
23860        };
23861        let buffer = buffer.read(cx);
23862        let buffer_id = buffer.remote_id();
23863        let opening_range =
23864            buffer.anchor_before(Point::new(0, 1))..buffer.anchor_after(Point::new(0, 3));
23865        let closing_range =
23866            buffer.anchor_before(Point::new(0, 6))..buffer.anchor_after(Point::new(0, 8));
23867        let mut linked_ranges = HashMap::default();
23868        linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
23869        editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
23870    });
23871    let mut completion_handle =
23872        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
23873            Ok(Some(lsp::CompletionResponse::Array(vec![
23874                lsp::CompletionItem {
23875                    label: "head".to_string(),
23876                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23877                        lsp::InsertReplaceEdit {
23878                            new_text: "head".to_string(),
23879                            insert: lsp::Range::new(
23880                                lsp::Position::new(0, 1),
23881                                lsp::Position::new(0, 3),
23882                            ),
23883                            replace: lsp::Range::new(
23884                                lsp::Position::new(0, 1),
23885                                lsp::Position::new(0, 3),
23886                            ),
23887                        },
23888                    )),
23889                    ..Default::default()
23890                },
23891            ])))
23892        });
23893    editor.update_in(cx, |editor, window, cx| {
23894        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
23895    });
23896    cx.run_until_parked();
23897    completion_handle.next().await.unwrap();
23898    editor.update(cx, |editor, _| {
23899        assert!(
23900            editor.context_menu_visible(),
23901            "Completion menu should be visible"
23902        );
23903    });
23904    editor.update_in(cx, |editor, window, cx| {
23905        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
23906    });
23907    cx.executor().run_until_parked();
23908    editor.update(cx, |editor, cx| {
23909        assert_eq!(editor.text(cx), "<head></head>");
23910    });
23911}
23912
23913#[gpui::test]
23914async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
23915    init_test(cx, |_| {});
23916
23917    let fs = FakeFs::new(cx.executor());
23918    fs.insert_tree(
23919        path!("/root"),
23920        json!({
23921            "a": {
23922                "main.rs": "fn main() {}",
23923            },
23924            "foo": {
23925                "bar": {
23926                    "external_file.rs": "pub mod external {}",
23927                }
23928            }
23929        }),
23930    )
23931    .await;
23932
23933    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
23934    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23935    language_registry.add(rust_lang());
23936    let _fake_servers = language_registry.register_fake_lsp(
23937        "Rust",
23938        FakeLspAdapter {
23939            ..FakeLspAdapter::default()
23940        },
23941    );
23942    let (workspace, cx) =
23943        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23944    let worktree_id = workspace.update(cx, |workspace, cx| {
23945        workspace.project().update(cx, |project, cx| {
23946            project.worktrees(cx).next().unwrap().read(cx).id()
23947        })
23948    });
23949
23950    let assert_language_servers_count =
23951        |expected: usize, context: &str, cx: &mut VisualTestContext| {
23952            project.update(cx, |project, cx| {
23953                let current = project
23954                    .lsp_store()
23955                    .read(cx)
23956                    .as_local()
23957                    .unwrap()
23958                    .language_servers
23959                    .len();
23960                assert_eq!(expected, current, "{context}");
23961            });
23962        };
23963
23964    assert_language_servers_count(
23965        0,
23966        "No servers should be running before any file is open",
23967        cx,
23968    );
23969    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23970    let main_editor = workspace
23971        .update_in(cx, |workspace, window, cx| {
23972            workspace.open_path(
23973                (worktree_id, "main.rs"),
23974                Some(pane.downgrade()),
23975                true,
23976                window,
23977                cx,
23978            )
23979        })
23980        .unwrap()
23981        .await
23982        .downcast::<Editor>()
23983        .unwrap();
23984    pane.update(cx, |pane, cx| {
23985        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23986        open_editor.update(cx, |editor, cx| {
23987            assert_eq!(
23988                editor.display_text(cx),
23989                "fn main() {}",
23990                "Original main.rs text on initial open",
23991            );
23992        });
23993        assert_eq!(open_editor, main_editor);
23994    });
23995    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
23996
23997    let external_editor = workspace
23998        .update_in(cx, |workspace, window, cx| {
23999            workspace.open_abs_path(
24000                PathBuf::from("/root/foo/bar/external_file.rs"),
24001                OpenOptions::default(),
24002                window,
24003                cx,
24004            )
24005        })
24006        .await
24007        .expect("opening external file")
24008        .downcast::<Editor>()
24009        .expect("downcasted external file's open element to editor");
24010    pane.update(cx, |pane, cx| {
24011        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24012        open_editor.update(cx, |editor, cx| {
24013            assert_eq!(
24014                editor.display_text(cx),
24015                "pub mod external {}",
24016                "External file is open now",
24017            );
24018        });
24019        assert_eq!(open_editor, external_editor);
24020    });
24021    assert_language_servers_count(
24022        1,
24023        "Second, external, *.rs file should join the existing server",
24024        cx,
24025    );
24026
24027    pane.update_in(cx, |pane, window, cx| {
24028        pane.close_active_item(&CloseActiveItem::default(), window, cx)
24029    })
24030    .await
24031    .unwrap();
24032    pane.update_in(cx, |pane, window, cx| {
24033        pane.navigate_backward(&Default::default(), window, cx);
24034    });
24035    cx.run_until_parked();
24036    pane.update(cx, |pane, cx| {
24037        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24038        open_editor.update(cx, |editor, cx| {
24039            assert_eq!(
24040                editor.display_text(cx),
24041                "pub mod external {}",
24042                "External file is open now",
24043            );
24044        });
24045    });
24046    assert_language_servers_count(
24047        1,
24048        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
24049        cx,
24050    );
24051
24052    cx.update(|_, cx| {
24053        workspace::reload(cx);
24054    });
24055    assert_language_servers_count(
24056        1,
24057        "After reloading the worktree with local and external files opened, only one project should be started",
24058        cx,
24059    );
24060}
24061
24062#[gpui::test]
24063async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
24064    init_test(cx, |_| {});
24065
24066    let mut cx = EditorTestContext::new(cx).await;
24067    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24068    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24069
24070    // test cursor move to start of each line on tab
24071    // for `if`, `elif`, `else`, `while`, `with` and `for`
24072    cx.set_state(indoc! {"
24073        def main():
24074        ˇ    for item in items:
24075        ˇ        while item.active:
24076        ˇ            if item.value > 10:
24077        ˇ                continue
24078        ˇ            elif item.value < 0:
24079        ˇ                break
24080        ˇ            else:
24081        ˇ                with item.context() as ctx:
24082        ˇ                    yield count
24083        ˇ        else:
24084        ˇ            log('while else')
24085        ˇ    else:
24086        ˇ        log('for else')
24087    "});
24088    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24089    cx.assert_editor_state(indoc! {"
24090        def main():
24091            ˇfor item in items:
24092                ˇwhile item.active:
24093                    ˇif item.value > 10:
24094                        ˇcontinue
24095                    ˇelif item.value < 0:
24096                        ˇbreak
24097                    ˇelse:
24098                        ˇwith item.context() as ctx:
24099                            ˇyield count
24100                ˇelse:
24101                    ˇlog('while else')
24102            ˇelse:
24103                ˇlog('for else')
24104    "});
24105    // test relative indent is preserved when tab
24106    // for `if`, `elif`, `else`, `while`, `with` and `for`
24107    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24108    cx.assert_editor_state(indoc! {"
24109        def main():
24110                ˇfor item in items:
24111                    ˇwhile item.active:
24112                        ˇif item.value > 10:
24113                            ˇcontinue
24114                        ˇelif item.value < 0:
24115                            ˇbreak
24116                        ˇelse:
24117                            ˇwith item.context() as ctx:
24118                                ˇyield count
24119                    ˇelse:
24120                        ˇlog('while else')
24121                ˇelse:
24122                    ˇlog('for else')
24123    "});
24124
24125    // test cursor move to start of each line on tab
24126    // for `try`, `except`, `else`, `finally`, `match` and `def`
24127    cx.set_state(indoc! {"
24128        def main():
24129        ˇ    try:
24130        ˇ        fetch()
24131        ˇ    except ValueError:
24132        ˇ        handle_error()
24133        ˇ    else:
24134        ˇ        match value:
24135        ˇ            case _:
24136        ˇ    finally:
24137        ˇ        def status():
24138        ˇ            return 0
24139    "});
24140    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24141    cx.assert_editor_state(indoc! {"
24142        def main():
24143            ˇtry:
24144                ˇfetch()
24145            ˇexcept ValueError:
24146                ˇhandle_error()
24147            ˇelse:
24148                ˇmatch value:
24149                    ˇcase _:
24150            ˇfinally:
24151                ˇdef status():
24152                    ˇreturn 0
24153    "});
24154    // test relative indent is preserved when tab
24155    // for `try`, `except`, `else`, `finally`, `match` and `def`
24156    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24157    cx.assert_editor_state(indoc! {"
24158        def main():
24159                ˇtry:
24160                    ˇfetch()
24161                ˇexcept ValueError:
24162                    ˇhandle_error()
24163                ˇelse:
24164                    ˇmatch value:
24165                        ˇcase _:
24166                ˇfinally:
24167                    ˇdef status():
24168                        ˇreturn 0
24169    "});
24170}
24171
24172#[gpui::test]
24173async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
24174    init_test(cx, |_| {});
24175
24176    let mut cx = EditorTestContext::new(cx).await;
24177    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24178    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24179
24180    // test `else` auto outdents when typed inside `if` block
24181    cx.set_state(indoc! {"
24182        def main():
24183            if i == 2:
24184                return
24185                ˇ
24186    "});
24187    cx.update_editor(|editor, window, cx| {
24188        editor.handle_input("else:", window, cx);
24189    });
24190    cx.assert_editor_state(indoc! {"
24191        def main():
24192            if i == 2:
24193                return
24194            else:ˇ
24195    "});
24196
24197    // test `except` auto outdents when typed inside `try` block
24198    cx.set_state(indoc! {"
24199        def main():
24200            try:
24201                i = 2
24202                ˇ
24203    "});
24204    cx.update_editor(|editor, window, cx| {
24205        editor.handle_input("except:", window, cx);
24206    });
24207    cx.assert_editor_state(indoc! {"
24208        def main():
24209            try:
24210                i = 2
24211            except:ˇ
24212    "});
24213
24214    // test `else` auto outdents when typed inside `except` block
24215    cx.set_state(indoc! {"
24216        def main():
24217            try:
24218                i = 2
24219            except:
24220                j = 2
24221                ˇ
24222    "});
24223    cx.update_editor(|editor, window, cx| {
24224        editor.handle_input("else:", window, cx);
24225    });
24226    cx.assert_editor_state(indoc! {"
24227        def main():
24228            try:
24229                i = 2
24230            except:
24231                j = 2
24232            else:ˇ
24233    "});
24234
24235    // test `finally` auto outdents when typed inside `else` block
24236    cx.set_state(indoc! {"
24237        def main():
24238            try:
24239                i = 2
24240            except:
24241                j = 2
24242            else:
24243                k = 2
24244                ˇ
24245    "});
24246    cx.update_editor(|editor, window, cx| {
24247        editor.handle_input("finally:", window, cx);
24248    });
24249    cx.assert_editor_state(indoc! {"
24250        def main():
24251            try:
24252                i = 2
24253            except:
24254                j = 2
24255            else:
24256                k = 2
24257            finally:ˇ
24258    "});
24259
24260    // test `else` does not outdents when typed inside `except` block right after for block
24261    cx.set_state(indoc! {"
24262        def main():
24263            try:
24264                i = 2
24265            except:
24266                for i in range(n):
24267                    pass
24268                ˇ
24269    "});
24270    cx.update_editor(|editor, window, cx| {
24271        editor.handle_input("else:", window, cx);
24272    });
24273    cx.assert_editor_state(indoc! {"
24274        def main():
24275            try:
24276                i = 2
24277            except:
24278                for i in range(n):
24279                    pass
24280                else:ˇ
24281    "});
24282
24283    // test `finally` auto outdents when typed inside `else` block right after for block
24284    cx.set_state(indoc! {"
24285        def main():
24286            try:
24287                i = 2
24288            except:
24289                j = 2
24290            else:
24291                for i in range(n):
24292                    pass
24293                ˇ
24294    "});
24295    cx.update_editor(|editor, window, cx| {
24296        editor.handle_input("finally:", window, cx);
24297    });
24298    cx.assert_editor_state(indoc! {"
24299        def main():
24300            try:
24301                i = 2
24302            except:
24303                j = 2
24304            else:
24305                for i in range(n):
24306                    pass
24307            finally:ˇ
24308    "});
24309
24310    // test `except` outdents to inner "try" block
24311    cx.set_state(indoc! {"
24312        def main():
24313            try:
24314                i = 2
24315                if i == 2:
24316                    try:
24317                        i = 3
24318                        ˇ
24319    "});
24320    cx.update_editor(|editor, window, cx| {
24321        editor.handle_input("except:", window, cx);
24322    });
24323    cx.assert_editor_state(indoc! {"
24324        def main():
24325            try:
24326                i = 2
24327                if i == 2:
24328                    try:
24329                        i = 3
24330                    except:ˇ
24331    "});
24332
24333    // test `except` outdents to outer "try" block
24334    cx.set_state(indoc! {"
24335        def main():
24336            try:
24337                i = 2
24338                if i == 2:
24339                    try:
24340                        i = 3
24341                ˇ
24342    "});
24343    cx.update_editor(|editor, window, cx| {
24344        editor.handle_input("except:", window, cx);
24345    });
24346    cx.assert_editor_state(indoc! {"
24347        def main():
24348            try:
24349                i = 2
24350                if i == 2:
24351                    try:
24352                        i = 3
24353            except:ˇ
24354    "});
24355
24356    // test `else` stays at correct indent when typed after `for` block
24357    cx.set_state(indoc! {"
24358        def main():
24359            for i in range(10):
24360                if i == 3:
24361                    break
24362            ˇ
24363    "});
24364    cx.update_editor(|editor, window, cx| {
24365        editor.handle_input("else:", window, cx);
24366    });
24367    cx.assert_editor_state(indoc! {"
24368        def main():
24369            for i in range(10):
24370                if i == 3:
24371                    break
24372            else:ˇ
24373    "});
24374
24375    // test does not outdent on typing after line with square brackets
24376    cx.set_state(indoc! {"
24377        def f() -> list[str]:
24378            ˇ
24379    "});
24380    cx.update_editor(|editor, window, cx| {
24381        editor.handle_input("a", window, cx);
24382    });
24383    cx.assert_editor_state(indoc! {"
24384        def f() -> list[str]:
2438524386    "});
24387
24388    // test does not outdent on typing : after case keyword
24389    cx.set_state(indoc! {"
24390        match 1:
24391            caseˇ
24392    "});
24393    cx.update_editor(|editor, window, cx| {
24394        editor.handle_input(":", window, cx);
24395    });
24396    cx.assert_editor_state(indoc! {"
24397        match 1:
24398            case:ˇ
24399    "});
24400}
24401
24402#[gpui::test]
24403async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
24404    init_test(cx, |_| {});
24405    update_test_language_settings(cx, |settings| {
24406        settings.defaults.extend_comment_on_newline = Some(false);
24407    });
24408    let mut cx = EditorTestContext::new(cx).await;
24409    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24410    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24411
24412    // test correct indent after newline on comment
24413    cx.set_state(indoc! {"
24414        # COMMENT:ˇ
24415    "});
24416    cx.update_editor(|editor, window, cx| {
24417        editor.newline(&Newline, window, cx);
24418    });
24419    cx.assert_editor_state(indoc! {"
24420        # COMMENT:
24421        ˇ
24422    "});
24423
24424    // test correct indent after newline in brackets
24425    cx.set_state(indoc! {"
24426        {ˇ}
24427    "});
24428    cx.update_editor(|editor, window, cx| {
24429        editor.newline(&Newline, window, cx);
24430    });
24431    cx.run_until_parked();
24432    cx.assert_editor_state(indoc! {"
24433        {
24434            ˇ
24435        }
24436    "});
24437
24438    cx.set_state(indoc! {"
24439        (ˇ)
24440    "});
24441    cx.update_editor(|editor, window, cx| {
24442        editor.newline(&Newline, window, cx);
24443    });
24444    cx.run_until_parked();
24445    cx.assert_editor_state(indoc! {"
24446        (
24447            ˇ
24448        )
24449    "});
24450
24451    // do not indent after empty lists or dictionaries
24452    cx.set_state(indoc! {"
24453        a = []ˇ
24454    "});
24455    cx.update_editor(|editor, window, cx| {
24456        editor.newline(&Newline, window, cx);
24457    });
24458    cx.run_until_parked();
24459    cx.assert_editor_state(indoc! {"
24460        a = []
24461        ˇ
24462    "});
24463}
24464
24465#[gpui::test]
24466async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
24467    init_test(cx, |_| {});
24468
24469    let mut cx = EditorTestContext::new(cx).await;
24470    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24471    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24472
24473    // test cursor move to start of each line on tab
24474    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
24475    cx.set_state(indoc! {"
24476        function main() {
24477        ˇ    for item in $items; do
24478        ˇ        while [ -n \"$item\" ]; do
24479        ˇ            if [ \"$value\" -gt 10 ]; then
24480        ˇ                continue
24481        ˇ            elif [ \"$value\" -lt 0 ]; then
24482        ˇ                break
24483        ˇ            else
24484        ˇ                echo \"$item\"
24485        ˇ            fi
24486        ˇ        done
24487        ˇ    done
24488        ˇ}
24489    "});
24490    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24491    cx.assert_editor_state(indoc! {"
24492        function main() {
24493            ˇfor item in $items; do
24494                ˇwhile [ -n \"$item\" ]; do
24495                    ˇif [ \"$value\" -gt 10 ]; then
24496                        ˇcontinue
24497                    ˇelif [ \"$value\" -lt 0 ]; then
24498                        ˇbreak
24499                    ˇelse
24500                        ˇecho \"$item\"
24501                    ˇfi
24502                ˇdone
24503            ˇdone
24504        ˇ}
24505    "});
24506    // test relative indent is preserved when tab
24507    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24508    cx.assert_editor_state(indoc! {"
24509        function main() {
24510                ˇfor item in $items; do
24511                    ˇwhile [ -n \"$item\" ]; do
24512                        ˇif [ \"$value\" -gt 10 ]; then
24513                            ˇcontinue
24514                        ˇelif [ \"$value\" -lt 0 ]; then
24515                            ˇbreak
24516                        ˇelse
24517                            ˇecho \"$item\"
24518                        ˇfi
24519                    ˇdone
24520                ˇdone
24521            ˇ}
24522    "});
24523
24524    // test cursor move to start of each line on tab
24525    // for `case` statement with patterns
24526    cx.set_state(indoc! {"
24527        function handle() {
24528        ˇ    case \"$1\" in
24529        ˇ        start)
24530        ˇ            echo \"a\"
24531        ˇ            ;;
24532        ˇ        stop)
24533        ˇ            echo \"b\"
24534        ˇ            ;;
24535        ˇ        *)
24536        ˇ            echo \"c\"
24537        ˇ            ;;
24538        ˇ    esac
24539        ˇ}
24540    "});
24541    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24542    cx.assert_editor_state(indoc! {"
24543        function handle() {
24544            ˇcase \"$1\" in
24545                ˇstart)
24546                    ˇecho \"a\"
24547                    ˇ;;
24548                ˇstop)
24549                    ˇecho \"b\"
24550                    ˇ;;
24551                ˇ*)
24552                    ˇecho \"c\"
24553                    ˇ;;
24554            ˇesac
24555        ˇ}
24556    "});
24557}
24558
24559#[gpui::test]
24560async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
24561    init_test(cx, |_| {});
24562
24563    let mut cx = EditorTestContext::new(cx).await;
24564    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24565    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24566
24567    // test indents on comment insert
24568    cx.set_state(indoc! {"
24569        function main() {
24570        ˇ    for item in $items; do
24571        ˇ        while [ -n \"$item\" ]; do
24572        ˇ            if [ \"$value\" -gt 10 ]; then
24573        ˇ                continue
24574        ˇ            elif [ \"$value\" -lt 0 ]; then
24575        ˇ                break
24576        ˇ            else
24577        ˇ                echo \"$item\"
24578        ˇ            fi
24579        ˇ        done
24580        ˇ    done
24581        ˇ}
24582    "});
24583    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
24584    cx.assert_editor_state(indoc! {"
24585        function main() {
24586        #ˇ    for item in $items; do
24587        #ˇ        while [ -n \"$item\" ]; do
24588        #ˇ            if [ \"$value\" -gt 10 ]; then
24589        #ˇ                continue
24590        #ˇ            elif [ \"$value\" -lt 0 ]; then
24591        #ˇ                break
24592        #ˇ            else
24593        #ˇ                echo \"$item\"
24594        #ˇ            fi
24595        #ˇ        done
24596        #ˇ    done
24597        #ˇ}
24598    "});
24599}
24600
24601#[gpui::test]
24602async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
24603    init_test(cx, |_| {});
24604
24605    let mut cx = EditorTestContext::new(cx).await;
24606    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24607    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24608
24609    // test `else` auto outdents when typed inside `if` block
24610    cx.set_state(indoc! {"
24611        if [ \"$1\" = \"test\" ]; then
24612            echo \"foo bar\"
24613            ˇ
24614    "});
24615    cx.update_editor(|editor, window, cx| {
24616        editor.handle_input("else", window, cx);
24617    });
24618    cx.assert_editor_state(indoc! {"
24619        if [ \"$1\" = \"test\" ]; then
24620            echo \"foo bar\"
24621        elseˇ
24622    "});
24623
24624    // test `elif` auto outdents when typed inside `if` block
24625    cx.set_state(indoc! {"
24626        if [ \"$1\" = \"test\" ]; then
24627            echo \"foo bar\"
24628            ˇ
24629    "});
24630    cx.update_editor(|editor, window, cx| {
24631        editor.handle_input("elif", window, cx);
24632    });
24633    cx.assert_editor_state(indoc! {"
24634        if [ \"$1\" = \"test\" ]; then
24635            echo \"foo bar\"
24636        elifˇ
24637    "});
24638
24639    // test `fi` auto outdents when typed inside `else` block
24640    cx.set_state(indoc! {"
24641        if [ \"$1\" = \"test\" ]; then
24642            echo \"foo bar\"
24643        else
24644            echo \"bar baz\"
24645            ˇ
24646    "});
24647    cx.update_editor(|editor, window, cx| {
24648        editor.handle_input("fi", window, cx);
24649    });
24650    cx.assert_editor_state(indoc! {"
24651        if [ \"$1\" = \"test\" ]; then
24652            echo \"foo bar\"
24653        else
24654            echo \"bar baz\"
24655        fiˇ
24656    "});
24657
24658    // test `done` auto outdents when typed inside `while` block
24659    cx.set_state(indoc! {"
24660        while read line; do
24661            echo \"$line\"
24662            ˇ
24663    "});
24664    cx.update_editor(|editor, window, cx| {
24665        editor.handle_input("done", window, cx);
24666    });
24667    cx.assert_editor_state(indoc! {"
24668        while read line; do
24669            echo \"$line\"
24670        doneˇ
24671    "});
24672
24673    // test `done` auto outdents when typed inside `for` block
24674    cx.set_state(indoc! {"
24675        for file in *.txt; do
24676            cat \"$file\"
24677            ˇ
24678    "});
24679    cx.update_editor(|editor, window, cx| {
24680        editor.handle_input("done", window, cx);
24681    });
24682    cx.assert_editor_state(indoc! {"
24683        for file in *.txt; do
24684            cat \"$file\"
24685        doneˇ
24686    "});
24687
24688    // test `esac` auto outdents when typed inside `case` block
24689    cx.set_state(indoc! {"
24690        case \"$1\" in
24691            start)
24692                echo \"foo bar\"
24693                ;;
24694            stop)
24695                echo \"bar baz\"
24696                ;;
24697            ˇ
24698    "});
24699    cx.update_editor(|editor, window, cx| {
24700        editor.handle_input("esac", window, cx);
24701    });
24702    cx.assert_editor_state(indoc! {"
24703        case \"$1\" in
24704            start)
24705                echo \"foo bar\"
24706                ;;
24707            stop)
24708                echo \"bar baz\"
24709                ;;
24710        esacˇ
24711    "});
24712
24713    // test `*)` auto outdents when typed inside `case` block
24714    cx.set_state(indoc! {"
24715        case \"$1\" in
24716            start)
24717                echo \"foo bar\"
24718                ;;
24719                ˇ
24720    "});
24721    cx.update_editor(|editor, window, cx| {
24722        editor.handle_input("*)", window, cx);
24723    });
24724    cx.assert_editor_state(indoc! {"
24725        case \"$1\" in
24726            start)
24727                echo \"foo bar\"
24728                ;;
24729            *)ˇ
24730    "});
24731
24732    // test `fi` outdents to correct level with nested if blocks
24733    cx.set_state(indoc! {"
24734        if [ \"$1\" = \"test\" ]; then
24735            echo \"outer if\"
24736            if [ \"$2\" = \"debug\" ]; then
24737                echo \"inner if\"
24738                ˇ
24739    "});
24740    cx.update_editor(|editor, window, cx| {
24741        editor.handle_input("fi", window, cx);
24742    });
24743    cx.assert_editor_state(indoc! {"
24744        if [ \"$1\" = \"test\" ]; then
24745            echo \"outer if\"
24746            if [ \"$2\" = \"debug\" ]; then
24747                echo \"inner if\"
24748            fiˇ
24749    "});
24750}
24751
24752#[gpui::test]
24753async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
24754    init_test(cx, |_| {});
24755    update_test_language_settings(cx, |settings| {
24756        settings.defaults.extend_comment_on_newline = Some(false);
24757    });
24758    let mut cx = EditorTestContext::new(cx).await;
24759    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24760    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24761
24762    // test correct indent after newline on comment
24763    cx.set_state(indoc! {"
24764        # COMMENT:ˇ
24765    "});
24766    cx.update_editor(|editor, window, cx| {
24767        editor.newline(&Newline, window, cx);
24768    });
24769    cx.assert_editor_state(indoc! {"
24770        # COMMENT:
24771        ˇ
24772    "});
24773
24774    // test correct indent after newline after `then`
24775    cx.set_state(indoc! {"
24776
24777        if [ \"$1\" = \"test\" ]; thenˇ
24778    "});
24779    cx.update_editor(|editor, window, cx| {
24780        editor.newline(&Newline, window, cx);
24781    });
24782    cx.run_until_parked();
24783    cx.assert_editor_state(indoc! {"
24784
24785        if [ \"$1\" = \"test\" ]; then
24786            ˇ
24787    "});
24788
24789    // test correct indent after newline after `else`
24790    cx.set_state(indoc! {"
24791        if [ \"$1\" = \"test\" ]; then
24792        elseˇ
24793    "});
24794    cx.update_editor(|editor, window, cx| {
24795        editor.newline(&Newline, window, cx);
24796    });
24797    cx.run_until_parked();
24798    cx.assert_editor_state(indoc! {"
24799        if [ \"$1\" = \"test\" ]; then
24800        else
24801            ˇ
24802    "});
24803
24804    // test correct indent after newline after `elif`
24805    cx.set_state(indoc! {"
24806        if [ \"$1\" = \"test\" ]; then
24807        elifˇ
24808    "});
24809    cx.update_editor(|editor, window, cx| {
24810        editor.newline(&Newline, window, cx);
24811    });
24812    cx.run_until_parked();
24813    cx.assert_editor_state(indoc! {"
24814        if [ \"$1\" = \"test\" ]; then
24815        elif
24816            ˇ
24817    "});
24818
24819    // test correct indent after newline after `do`
24820    cx.set_state(indoc! {"
24821        for file in *.txt; doˇ
24822    "});
24823    cx.update_editor(|editor, window, cx| {
24824        editor.newline(&Newline, window, cx);
24825    });
24826    cx.run_until_parked();
24827    cx.assert_editor_state(indoc! {"
24828        for file in *.txt; do
24829            ˇ
24830    "});
24831
24832    // test correct indent after newline after case pattern
24833    cx.set_state(indoc! {"
24834        case \"$1\" in
24835            start)ˇ
24836    "});
24837    cx.update_editor(|editor, window, cx| {
24838        editor.newline(&Newline, window, cx);
24839    });
24840    cx.run_until_parked();
24841    cx.assert_editor_state(indoc! {"
24842        case \"$1\" in
24843            start)
24844                ˇ
24845    "});
24846
24847    // test correct indent after newline after case pattern
24848    cx.set_state(indoc! {"
24849        case \"$1\" in
24850            start)
24851                ;;
24852            *)ˇ
24853    "});
24854    cx.update_editor(|editor, window, cx| {
24855        editor.newline(&Newline, window, cx);
24856    });
24857    cx.run_until_parked();
24858    cx.assert_editor_state(indoc! {"
24859        case \"$1\" in
24860            start)
24861                ;;
24862            *)
24863                ˇ
24864    "});
24865
24866    // test correct indent after newline after function opening brace
24867    cx.set_state(indoc! {"
24868        function test() {ˇ}
24869    "});
24870    cx.update_editor(|editor, window, cx| {
24871        editor.newline(&Newline, window, cx);
24872    });
24873    cx.run_until_parked();
24874    cx.assert_editor_state(indoc! {"
24875        function test() {
24876            ˇ
24877        }
24878    "});
24879
24880    // test no extra indent after semicolon on same line
24881    cx.set_state(indoc! {"
24882        echo \"test\"24883    "});
24884    cx.update_editor(|editor, window, cx| {
24885        editor.newline(&Newline, window, cx);
24886    });
24887    cx.run_until_parked();
24888    cx.assert_editor_state(indoc! {"
24889        echo \"test\";
24890        ˇ
24891    "});
24892}
24893
24894fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
24895    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
24896    point..point
24897}
24898
24899#[track_caller]
24900fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
24901    let (text, ranges) = marked_text_ranges(marked_text, true);
24902    assert_eq!(editor.text(cx), text);
24903    assert_eq!(
24904        editor.selections.ranges(cx),
24905        ranges,
24906        "Assert selections are {}",
24907        marked_text
24908    );
24909}
24910
24911pub fn handle_signature_help_request(
24912    cx: &mut EditorLspTestContext,
24913    mocked_response: lsp::SignatureHelp,
24914) -> impl Future<Output = ()> + use<> {
24915    let mut request =
24916        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
24917            let mocked_response = mocked_response.clone();
24918            async move { Ok(Some(mocked_response)) }
24919        });
24920
24921    async move {
24922        request.next().await;
24923    }
24924}
24925
24926#[track_caller]
24927pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
24928    cx.update_editor(|editor, _, _| {
24929        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
24930            let entries = menu.entries.borrow();
24931            let entries = entries
24932                .iter()
24933                .map(|entry| entry.string.as_str())
24934                .collect::<Vec<_>>();
24935            assert_eq!(entries, expected);
24936        } else {
24937            panic!("Expected completions menu");
24938        }
24939    });
24940}
24941
24942/// Handle completion request passing a marked string specifying where the completion
24943/// should be triggered from using '|' character, what range should be replaced, and what completions
24944/// should be returned using '<' and '>' to delimit the range.
24945///
24946/// Also see `handle_completion_request_with_insert_and_replace`.
24947#[track_caller]
24948pub fn handle_completion_request(
24949    marked_string: &str,
24950    completions: Vec<&'static str>,
24951    is_incomplete: bool,
24952    counter: Arc<AtomicUsize>,
24953    cx: &mut EditorLspTestContext,
24954) -> impl Future<Output = ()> {
24955    let complete_from_marker: TextRangeMarker = '|'.into();
24956    let replace_range_marker: TextRangeMarker = ('<', '>').into();
24957    let (_, mut marked_ranges) = marked_text_ranges_by(
24958        marked_string,
24959        vec![complete_from_marker.clone(), replace_range_marker.clone()],
24960    );
24961
24962    let complete_from_position =
24963        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
24964    let replace_range =
24965        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
24966
24967    let mut request =
24968        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
24969            let completions = completions.clone();
24970            counter.fetch_add(1, atomic::Ordering::Release);
24971            async move {
24972                assert_eq!(params.text_document_position.text_document.uri, url.clone());
24973                assert_eq!(
24974                    params.text_document_position.position,
24975                    complete_from_position
24976                );
24977                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
24978                    is_incomplete,
24979                    item_defaults: None,
24980                    items: completions
24981                        .iter()
24982                        .map(|completion_text| lsp::CompletionItem {
24983                            label: completion_text.to_string(),
24984                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
24985                                range: replace_range,
24986                                new_text: completion_text.to_string(),
24987                            })),
24988                            ..Default::default()
24989                        })
24990                        .collect(),
24991                })))
24992            }
24993        });
24994
24995    async move {
24996        request.next().await;
24997    }
24998}
24999
25000/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
25001/// given instead, which also contains an `insert` range.
25002///
25003/// This function uses markers to define ranges:
25004/// - `|` marks the cursor position
25005/// - `<>` marks the replace range
25006/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
25007pub fn handle_completion_request_with_insert_and_replace(
25008    cx: &mut EditorLspTestContext,
25009    marked_string: &str,
25010    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
25011    counter: Arc<AtomicUsize>,
25012) -> impl Future<Output = ()> {
25013    let complete_from_marker: TextRangeMarker = '|'.into();
25014    let replace_range_marker: TextRangeMarker = ('<', '>').into();
25015    let insert_range_marker: TextRangeMarker = ('{', '}').into();
25016
25017    let (_, mut marked_ranges) = marked_text_ranges_by(
25018        marked_string,
25019        vec![
25020            complete_from_marker.clone(),
25021            replace_range_marker.clone(),
25022            insert_range_marker.clone(),
25023        ],
25024    );
25025
25026    let complete_from_position =
25027        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
25028    let replace_range =
25029        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
25030
25031    let insert_range = match marked_ranges.remove(&insert_range_marker) {
25032        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
25033        _ => lsp::Range {
25034            start: replace_range.start,
25035            end: complete_from_position,
25036        },
25037    };
25038
25039    let mut request =
25040        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
25041            let completions = completions.clone();
25042            counter.fetch_add(1, atomic::Ordering::Release);
25043            async move {
25044                assert_eq!(params.text_document_position.text_document.uri, url.clone());
25045                assert_eq!(
25046                    params.text_document_position.position, complete_from_position,
25047                    "marker `|` position doesn't match",
25048                );
25049                Ok(Some(lsp::CompletionResponse::Array(
25050                    completions
25051                        .iter()
25052                        .map(|(label, new_text)| lsp::CompletionItem {
25053                            label: label.to_string(),
25054                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
25055                                lsp::InsertReplaceEdit {
25056                                    insert: insert_range,
25057                                    replace: replace_range,
25058                                    new_text: new_text.to_string(),
25059                                },
25060                            )),
25061                            ..Default::default()
25062                        })
25063                        .collect(),
25064                )))
25065            }
25066        });
25067
25068    async move {
25069        request.next().await;
25070    }
25071}
25072
25073fn handle_resolve_completion_request(
25074    cx: &mut EditorLspTestContext,
25075    edits: Option<Vec<(&'static str, &'static str)>>,
25076) -> impl Future<Output = ()> {
25077    let edits = edits.map(|edits| {
25078        edits
25079            .iter()
25080            .map(|(marked_string, new_text)| {
25081                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
25082                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
25083                lsp::TextEdit::new(replace_range, new_text.to_string())
25084            })
25085            .collect::<Vec<_>>()
25086    });
25087
25088    let mut request =
25089        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
25090            let edits = edits.clone();
25091            async move {
25092                Ok(lsp::CompletionItem {
25093                    additional_text_edits: edits,
25094                    ..Default::default()
25095                })
25096            }
25097        });
25098
25099    async move {
25100        request.next().await;
25101    }
25102}
25103
25104pub(crate) fn update_test_language_settings(
25105    cx: &mut TestAppContext,
25106    f: impl Fn(&mut AllLanguageSettingsContent),
25107) {
25108    cx.update(|cx| {
25109        SettingsStore::update_global(cx, |store, cx| {
25110            store.update_user_settings(cx, |settings| f(&mut settings.project.all_languages));
25111        });
25112    });
25113}
25114
25115pub(crate) fn update_test_project_settings(
25116    cx: &mut TestAppContext,
25117    f: impl Fn(&mut ProjectSettingsContent),
25118) {
25119    cx.update(|cx| {
25120        SettingsStore::update_global(cx, |store, cx| {
25121            store.update_user_settings(cx, |settings| f(&mut settings.project));
25122        });
25123    });
25124}
25125
25126pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
25127    cx.update(|cx| {
25128        assets::Assets.load_test_fonts(cx);
25129        let store = SettingsStore::test(cx);
25130        cx.set_global(store);
25131        theme::init(theme::LoadThemes::JustBase, cx);
25132        release_channel::init(SemanticVersion::default(), cx);
25133        client::init_settings(cx);
25134        language::init(cx);
25135        Project::init_settings(cx);
25136        workspace::init_settings(cx);
25137        crate::init(cx);
25138    });
25139    zlog::init_test();
25140    update_test_language_settings(cx, f);
25141}
25142
25143#[track_caller]
25144fn assert_hunk_revert(
25145    not_reverted_text_with_selections: &str,
25146    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
25147    expected_reverted_text_with_selections: &str,
25148    base_text: &str,
25149    cx: &mut EditorLspTestContext,
25150) {
25151    cx.set_state(not_reverted_text_with_selections);
25152    cx.set_head_text(base_text);
25153    cx.executor().run_until_parked();
25154
25155    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
25156        let snapshot = editor.snapshot(window, cx);
25157        let reverted_hunk_statuses = snapshot
25158            .buffer_snapshot
25159            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
25160            .map(|hunk| hunk.status().kind)
25161            .collect::<Vec<_>>();
25162
25163        editor.git_restore(&Default::default(), window, cx);
25164        reverted_hunk_statuses
25165    });
25166    cx.executor().run_until_parked();
25167    cx.assert_editor_state(expected_reverted_text_with_selections);
25168    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
25169}
25170
25171#[gpui::test(iterations = 10)]
25172async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
25173    init_test(cx, |_| {});
25174
25175    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
25176    let counter = diagnostic_requests.clone();
25177
25178    let fs = FakeFs::new(cx.executor());
25179    fs.insert_tree(
25180        path!("/a"),
25181        json!({
25182            "first.rs": "fn main() { let a = 5; }",
25183            "second.rs": "// Test file",
25184        }),
25185    )
25186    .await;
25187
25188    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25189    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25190    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25191
25192    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25193    language_registry.add(rust_lang());
25194    let mut fake_servers = language_registry.register_fake_lsp(
25195        "Rust",
25196        FakeLspAdapter {
25197            capabilities: lsp::ServerCapabilities {
25198                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
25199                    lsp::DiagnosticOptions {
25200                        identifier: None,
25201                        inter_file_dependencies: true,
25202                        workspace_diagnostics: true,
25203                        work_done_progress_options: Default::default(),
25204                    },
25205                )),
25206                ..Default::default()
25207            },
25208            ..Default::default()
25209        },
25210    );
25211
25212    let editor = workspace
25213        .update(cx, |workspace, window, cx| {
25214            workspace.open_abs_path(
25215                PathBuf::from(path!("/a/first.rs")),
25216                OpenOptions::default(),
25217                window,
25218                cx,
25219            )
25220        })
25221        .unwrap()
25222        .await
25223        .unwrap()
25224        .downcast::<Editor>()
25225        .unwrap();
25226    let fake_server = fake_servers.next().await.unwrap();
25227    let server_id = fake_server.server.server_id();
25228    let mut first_request = fake_server
25229        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
25230            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
25231            let result_id = Some(new_result_id.to_string());
25232            assert_eq!(
25233                params.text_document.uri,
25234                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25235            );
25236            async move {
25237                Ok(lsp::DocumentDiagnosticReportResult::Report(
25238                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
25239                        related_documents: None,
25240                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
25241                            items: Vec::new(),
25242                            result_id,
25243                        },
25244                    }),
25245                ))
25246            }
25247        });
25248
25249    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
25250        project.update(cx, |project, cx| {
25251            let buffer_id = editor
25252                .read(cx)
25253                .buffer()
25254                .read(cx)
25255                .as_singleton()
25256                .expect("created a singleton buffer")
25257                .read(cx)
25258                .remote_id();
25259            let buffer_result_id = project
25260                .lsp_store()
25261                .read(cx)
25262                .result_id(server_id, buffer_id, cx);
25263            assert_eq!(expected, buffer_result_id);
25264        });
25265    };
25266
25267    ensure_result_id(None, cx);
25268    cx.executor().advance_clock(Duration::from_millis(60));
25269    cx.executor().run_until_parked();
25270    assert_eq!(
25271        diagnostic_requests.load(atomic::Ordering::Acquire),
25272        1,
25273        "Opening file should trigger diagnostic request"
25274    );
25275    first_request
25276        .next()
25277        .await
25278        .expect("should have sent the first diagnostics pull request");
25279    ensure_result_id(Some("1".to_string()), cx);
25280
25281    // Editing should trigger diagnostics
25282    editor.update_in(cx, |editor, window, cx| {
25283        editor.handle_input("2", window, cx)
25284    });
25285    cx.executor().advance_clock(Duration::from_millis(60));
25286    cx.executor().run_until_parked();
25287    assert_eq!(
25288        diagnostic_requests.load(atomic::Ordering::Acquire),
25289        2,
25290        "Editing should trigger diagnostic request"
25291    );
25292    ensure_result_id(Some("2".to_string()), cx);
25293
25294    // Moving cursor should not trigger diagnostic request
25295    editor.update_in(cx, |editor, window, cx| {
25296        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25297            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
25298        });
25299    });
25300    cx.executor().advance_clock(Duration::from_millis(60));
25301    cx.executor().run_until_parked();
25302    assert_eq!(
25303        diagnostic_requests.load(atomic::Ordering::Acquire),
25304        2,
25305        "Cursor movement should not trigger diagnostic request"
25306    );
25307    ensure_result_id(Some("2".to_string()), cx);
25308    // Multiple rapid edits should be debounced
25309    for _ in 0..5 {
25310        editor.update_in(cx, |editor, window, cx| {
25311            editor.handle_input("x", window, cx)
25312        });
25313    }
25314    cx.executor().advance_clock(Duration::from_millis(60));
25315    cx.executor().run_until_parked();
25316
25317    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
25318    assert!(
25319        final_requests <= 4,
25320        "Multiple rapid edits should be debounced (got {final_requests} requests)",
25321    );
25322    ensure_result_id(Some(final_requests.to_string()), cx);
25323}
25324
25325#[gpui::test]
25326async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
25327    // Regression test for issue #11671
25328    // Previously, adding a cursor after moving multiple cursors would reset
25329    // the cursor count instead of adding to the existing cursors.
25330    init_test(cx, |_| {});
25331    let mut cx = EditorTestContext::new(cx).await;
25332
25333    // Create a simple buffer with cursor at start
25334    cx.set_state(indoc! {"
25335        ˇaaaa
25336        bbbb
25337        cccc
25338        dddd
25339        eeee
25340        ffff
25341        gggg
25342        hhhh"});
25343
25344    // Add 2 cursors below (so we have 3 total)
25345    cx.update_editor(|editor, window, cx| {
25346        editor.add_selection_below(&Default::default(), window, cx);
25347        editor.add_selection_below(&Default::default(), window, cx);
25348    });
25349
25350    // Verify we have 3 cursors
25351    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
25352    assert_eq!(
25353        initial_count, 3,
25354        "Should have 3 cursors after adding 2 below"
25355    );
25356
25357    // Move down one line
25358    cx.update_editor(|editor, window, cx| {
25359        editor.move_down(&MoveDown, window, cx);
25360    });
25361
25362    // Add another cursor below
25363    cx.update_editor(|editor, window, cx| {
25364        editor.add_selection_below(&Default::default(), window, cx);
25365    });
25366
25367    // Should now have 4 cursors (3 original + 1 new)
25368    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
25369    assert_eq!(
25370        final_count, 4,
25371        "Should have 4 cursors after moving and adding another"
25372    );
25373}
25374
25375#[gpui::test(iterations = 10)]
25376async fn test_document_colors(cx: &mut TestAppContext) {
25377    let expected_color = Rgba {
25378        r: 0.33,
25379        g: 0.33,
25380        b: 0.33,
25381        a: 0.33,
25382    };
25383
25384    init_test(cx, |_| {});
25385
25386    let fs = FakeFs::new(cx.executor());
25387    fs.insert_tree(
25388        path!("/a"),
25389        json!({
25390            "first.rs": "fn main() { let a = 5; }",
25391        }),
25392    )
25393    .await;
25394
25395    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25396    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25397    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25398
25399    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25400    language_registry.add(rust_lang());
25401    let mut fake_servers = language_registry.register_fake_lsp(
25402        "Rust",
25403        FakeLspAdapter {
25404            capabilities: lsp::ServerCapabilities {
25405                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
25406                ..lsp::ServerCapabilities::default()
25407            },
25408            name: "rust-analyzer",
25409            ..FakeLspAdapter::default()
25410        },
25411    );
25412    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
25413        "Rust",
25414        FakeLspAdapter {
25415            capabilities: lsp::ServerCapabilities {
25416                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
25417                ..lsp::ServerCapabilities::default()
25418            },
25419            name: "not-rust-analyzer",
25420            ..FakeLspAdapter::default()
25421        },
25422    );
25423
25424    let editor = workspace
25425        .update(cx, |workspace, window, cx| {
25426            workspace.open_abs_path(
25427                PathBuf::from(path!("/a/first.rs")),
25428                OpenOptions::default(),
25429                window,
25430                cx,
25431            )
25432        })
25433        .unwrap()
25434        .await
25435        .unwrap()
25436        .downcast::<Editor>()
25437        .unwrap();
25438    let fake_language_server = fake_servers.next().await.unwrap();
25439    let fake_language_server_without_capabilities =
25440        fake_servers_without_capabilities.next().await.unwrap();
25441    let requests_made = Arc::new(AtomicUsize::new(0));
25442    let closure_requests_made = Arc::clone(&requests_made);
25443    let mut color_request_handle = fake_language_server
25444        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
25445            let requests_made = Arc::clone(&closure_requests_made);
25446            async move {
25447                assert_eq!(
25448                    params.text_document.uri,
25449                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25450                );
25451                requests_made.fetch_add(1, atomic::Ordering::Release);
25452                Ok(vec![
25453                    lsp::ColorInformation {
25454                        range: lsp::Range {
25455                            start: lsp::Position {
25456                                line: 0,
25457                                character: 0,
25458                            },
25459                            end: lsp::Position {
25460                                line: 0,
25461                                character: 1,
25462                            },
25463                        },
25464                        color: lsp::Color {
25465                            red: 0.33,
25466                            green: 0.33,
25467                            blue: 0.33,
25468                            alpha: 0.33,
25469                        },
25470                    },
25471                    lsp::ColorInformation {
25472                        range: lsp::Range {
25473                            start: lsp::Position {
25474                                line: 0,
25475                                character: 0,
25476                            },
25477                            end: lsp::Position {
25478                                line: 0,
25479                                character: 1,
25480                            },
25481                        },
25482                        color: lsp::Color {
25483                            red: 0.33,
25484                            green: 0.33,
25485                            blue: 0.33,
25486                            alpha: 0.33,
25487                        },
25488                    },
25489                ])
25490            }
25491        });
25492
25493    let _handle = fake_language_server_without_capabilities
25494        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
25495            panic!("Should not be called");
25496        });
25497    cx.executor().advance_clock(Duration::from_millis(100));
25498    color_request_handle.next().await.unwrap();
25499    cx.run_until_parked();
25500    assert_eq!(
25501        1,
25502        requests_made.load(atomic::Ordering::Acquire),
25503        "Should query for colors once per editor open"
25504    );
25505    editor.update_in(cx, |editor, _, cx| {
25506        assert_eq!(
25507            vec![expected_color],
25508            extract_color_inlays(editor, cx),
25509            "Should have an initial inlay"
25510        );
25511    });
25512
25513    // opening another file in a split should not influence the LSP query counter
25514    workspace
25515        .update(cx, |workspace, window, cx| {
25516            assert_eq!(
25517                workspace.panes().len(),
25518                1,
25519                "Should have one pane with one editor"
25520            );
25521            workspace.move_item_to_pane_in_direction(
25522                &MoveItemToPaneInDirection {
25523                    direction: SplitDirection::Right,
25524                    focus: false,
25525                    clone: true,
25526                },
25527                window,
25528                cx,
25529            );
25530        })
25531        .unwrap();
25532    cx.run_until_parked();
25533    workspace
25534        .update(cx, |workspace, _, cx| {
25535            let panes = workspace.panes();
25536            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
25537            for pane in panes {
25538                let editor = pane
25539                    .read(cx)
25540                    .active_item()
25541                    .and_then(|item| item.downcast::<Editor>())
25542                    .expect("Should have opened an editor in each split");
25543                let editor_file = editor
25544                    .read(cx)
25545                    .buffer()
25546                    .read(cx)
25547                    .as_singleton()
25548                    .expect("test deals with singleton buffers")
25549                    .read(cx)
25550                    .file()
25551                    .expect("test buffese should have a file")
25552                    .path();
25553                assert_eq!(
25554                    editor_file.as_ref(),
25555                    Path::new("first.rs"),
25556                    "Both editors should be opened for the same file"
25557                )
25558            }
25559        })
25560        .unwrap();
25561
25562    cx.executor().advance_clock(Duration::from_millis(500));
25563    let save = editor.update_in(cx, |editor, window, cx| {
25564        editor.move_to_end(&MoveToEnd, window, cx);
25565        editor.handle_input("dirty", window, cx);
25566        editor.save(
25567            SaveOptions {
25568                format: true,
25569                autosave: true,
25570            },
25571            project.clone(),
25572            window,
25573            cx,
25574        )
25575    });
25576    save.await.unwrap();
25577
25578    color_request_handle.next().await.unwrap();
25579    cx.run_until_parked();
25580    assert_eq!(
25581        3,
25582        requests_made.load(atomic::Ordering::Acquire),
25583        "Should query for colors once per save and once per formatting after save"
25584    );
25585
25586    drop(editor);
25587    let close = workspace
25588        .update(cx, |workspace, window, cx| {
25589            workspace.active_pane().update(cx, |pane, cx| {
25590                pane.close_active_item(&CloseActiveItem::default(), window, cx)
25591            })
25592        })
25593        .unwrap();
25594    close.await.unwrap();
25595    let close = workspace
25596        .update(cx, |workspace, window, cx| {
25597            workspace.active_pane().update(cx, |pane, cx| {
25598                pane.close_active_item(&CloseActiveItem::default(), window, cx)
25599            })
25600        })
25601        .unwrap();
25602    close.await.unwrap();
25603    assert_eq!(
25604        3,
25605        requests_made.load(atomic::Ordering::Acquire),
25606        "After saving and closing all editors, no extra requests should be made"
25607    );
25608    workspace
25609        .update(cx, |workspace, _, cx| {
25610            assert!(
25611                workspace.active_item(cx).is_none(),
25612                "Should close all editors"
25613            )
25614        })
25615        .unwrap();
25616
25617    workspace
25618        .update(cx, |workspace, window, cx| {
25619            workspace.active_pane().update(cx, |pane, cx| {
25620                pane.navigate_backward(&Default::default(), window, cx);
25621            })
25622        })
25623        .unwrap();
25624    cx.executor().advance_clock(Duration::from_millis(100));
25625    cx.run_until_parked();
25626    let editor = workspace
25627        .update(cx, |workspace, _, cx| {
25628            workspace
25629                .active_item(cx)
25630                .expect("Should have reopened the editor again after navigating back")
25631                .downcast::<Editor>()
25632                .expect("Should be an editor")
25633        })
25634        .unwrap();
25635    color_request_handle.next().await.unwrap();
25636    assert_eq!(
25637        3,
25638        requests_made.load(atomic::Ordering::Acquire),
25639        "Cache should be reused on buffer close and reopen"
25640    );
25641    editor.update(cx, |editor, cx| {
25642        assert_eq!(
25643            vec![expected_color],
25644            extract_color_inlays(editor, cx),
25645            "Should have an initial inlay"
25646        );
25647    });
25648}
25649
25650#[gpui::test]
25651async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
25652    init_test(cx, |_| {});
25653    let (editor, cx) = cx.add_window_view(Editor::single_line);
25654    editor.update_in(cx, |editor, window, cx| {
25655        editor.set_text("oops\n\nwow\n", window, cx)
25656    });
25657    cx.run_until_parked();
25658    editor.update(cx, |editor, cx| {
25659        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
25660    });
25661    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
25662    cx.run_until_parked();
25663    editor.update(cx, |editor, cx| {
25664        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
25665    });
25666}
25667
25668#[gpui::test]
25669async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
25670    init_test(cx, |_| {});
25671
25672    cx.update(|cx| {
25673        register_project_item::<Editor>(cx);
25674    });
25675
25676    let fs = FakeFs::new(cx.executor());
25677    fs.insert_tree("/root1", json!({})).await;
25678    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
25679        .await;
25680
25681    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
25682    let (workspace, cx) =
25683        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
25684
25685    let worktree_id = project.update(cx, |project, cx| {
25686        project.worktrees(cx).next().unwrap().read(cx).id()
25687    });
25688
25689    let handle = workspace
25690        .update_in(cx, |workspace, window, cx| {
25691            let project_path = (worktree_id, "one.pdf");
25692            workspace.open_path(project_path, None, true, window, cx)
25693        })
25694        .await
25695        .unwrap();
25696
25697    assert_eq!(
25698        handle.to_any().entity_type(),
25699        TypeId::of::<InvalidBufferView>()
25700    );
25701}
25702
25703#[gpui::test]
25704async fn test_select_next_prev_syntax_node(cx: &mut TestAppContext) {
25705    init_test(cx, |_| {});
25706
25707    let language = Arc::new(Language::new(
25708        LanguageConfig::default(),
25709        Some(tree_sitter_rust::LANGUAGE.into()),
25710    ));
25711
25712    // Test hierarchical sibling navigation
25713    let text = r#"
25714        fn outer() {
25715            if condition {
25716                let a = 1;
25717            }
25718            let b = 2;
25719        }
25720
25721        fn another() {
25722            let c = 3;
25723        }
25724    "#;
25725
25726    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
25727    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
25728    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
25729
25730    // Wait for parsing to complete
25731    editor
25732        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
25733        .await;
25734
25735    editor.update_in(cx, |editor, window, cx| {
25736        // Start by selecting "let a = 1;" inside the if block
25737        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25738            s.select_display_ranges([
25739                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 26)
25740            ]);
25741        });
25742
25743        let initial_selection = editor.selections.display_ranges(cx);
25744        assert_eq!(initial_selection.len(), 1, "Should have one selection");
25745
25746        // Test select next sibling - should move up levels to find the next sibling
25747        // Since "let a = 1;" has no siblings in the if block, it should move up
25748        // to find "let b = 2;" which is a sibling of the if block
25749        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
25750        let next_selection = editor.selections.display_ranges(cx);
25751
25752        // Should have a selection and it should be different from the initial
25753        assert_eq!(
25754            next_selection.len(),
25755            1,
25756            "Should have one selection after next"
25757        );
25758        assert_ne!(
25759            next_selection[0], initial_selection[0],
25760            "Next sibling selection should be different"
25761        );
25762
25763        // Test hierarchical navigation by going to the end of the current function
25764        // and trying to navigate to the next function
25765        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25766            s.select_display_ranges([
25767                DisplayPoint::new(DisplayRow(5), 12)..DisplayPoint::new(DisplayRow(5), 22)
25768            ]);
25769        });
25770
25771        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
25772        let function_next_selection = editor.selections.display_ranges(cx);
25773
25774        // Should move to the next function
25775        assert_eq!(
25776            function_next_selection.len(),
25777            1,
25778            "Should have one selection after function next"
25779        );
25780
25781        // Test select previous sibling navigation
25782        editor.select_prev_syntax_node(&SelectPreviousSyntaxNode, window, cx);
25783        let prev_selection = editor.selections.display_ranges(cx);
25784
25785        // Should have a selection and it should be different
25786        assert_eq!(
25787            prev_selection.len(),
25788            1,
25789            "Should have one selection after prev"
25790        );
25791        assert_ne!(
25792            prev_selection[0], function_next_selection[0],
25793            "Previous sibling selection should be different from next"
25794        );
25795    });
25796}
25797
25798#[gpui::test]
25799async fn test_next_prev_document_highlight(cx: &mut TestAppContext) {
25800    init_test(cx, |_| {});
25801
25802    let mut cx = EditorTestContext::new(cx).await;
25803    cx.set_state(
25804        "let ˇvariable = 42;
25805let another = variable + 1;
25806let result = variable * 2;",
25807    );
25808
25809    // Set up document highlights manually (simulating LSP response)
25810    cx.update_editor(|editor, _window, cx| {
25811        let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
25812
25813        // Create highlights for "variable" occurrences
25814        let highlight_ranges = [
25815            Point::new(0, 4)..Point::new(0, 12),  // First "variable"
25816            Point::new(1, 14)..Point::new(1, 22), // Second "variable"
25817            Point::new(2, 13)..Point::new(2, 21), // Third "variable"
25818        ];
25819
25820        let anchor_ranges: Vec<_> = highlight_ranges
25821            .iter()
25822            .map(|range| range.clone().to_anchors(&buffer_snapshot))
25823            .collect();
25824
25825        editor.highlight_background::<DocumentHighlightRead>(
25826            &anchor_ranges,
25827            |theme| theme.colors().editor_document_highlight_read_background,
25828            cx,
25829        );
25830    });
25831
25832    // Go to next highlight - should move to second "variable"
25833    cx.update_editor(|editor, window, cx| {
25834        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
25835    });
25836    cx.assert_editor_state(
25837        "let variable = 42;
25838let another = ˇvariable + 1;
25839let result = variable * 2;",
25840    );
25841
25842    // Go to next highlight - should move to third "variable"
25843    cx.update_editor(|editor, window, cx| {
25844        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
25845    });
25846    cx.assert_editor_state(
25847        "let variable = 42;
25848let another = variable + 1;
25849let result = ˇvariable * 2;",
25850    );
25851
25852    // Go to next highlight - should stay at third "variable" (no wrap-around)
25853    cx.update_editor(|editor, window, cx| {
25854        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
25855    });
25856    cx.assert_editor_state(
25857        "let variable = 42;
25858let another = variable + 1;
25859let result = ˇvariable * 2;",
25860    );
25861
25862    // Now test going backwards from third position
25863    cx.update_editor(|editor, window, cx| {
25864        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
25865    });
25866    cx.assert_editor_state(
25867        "let variable = 42;
25868let another = ˇvariable + 1;
25869let result = variable * 2;",
25870    );
25871
25872    // Go to previous highlight - should move to first "variable"
25873    cx.update_editor(|editor, window, cx| {
25874        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
25875    });
25876    cx.assert_editor_state(
25877        "let ˇvariable = 42;
25878let another = variable + 1;
25879let result = variable * 2;",
25880    );
25881
25882    // Go to previous highlight - should stay on first "variable"
25883    cx.update_editor(|editor, window, cx| {
25884        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
25885    });
25886    cx.assert_editor_state(
25887        "let ˇvariable = 42;
25888let another = variable + 1;
25889let result = variable * 2;",
25890    );
25891}
25892
25893#[track_caller]
25894fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
25895    editor
25896        .all_inlays(cx)
25897        .into_iter()
25898        .filter_map(|inlay| inlay.get_color())
25899        .map(Rgba::from)
25900        .collect()
25901}