editor_tests.rs

    1use super::*;
    2use crate::{
    3    JoinLines,
    4    code_context_menus::CodeContextMenu,
    5    edit_prediction_tests::FakeEditPredictionDelegate,
    6    element::StickyHeader,
    7    linked_editing_ranges::LinkedEditingRanges,
    8    scroll::scroll_amount::ScrollAmount,
    9    test::{
   10        assert_text_with_selections, build_editor,
   11        editor_lsp_test_context::{EditorLspTestContext, git_commit_lang},
   12        editor_test_context::EditorTestContext,
   13        select_ranges,
   14    },
   15};
   16use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};
   17use collections::HashMap;
   18use futures::{StreamExt, channel::oneshot};
   19use gpui::{
   20    BackgroundExecutor, DismissEvent, Rgba, TestAppContext, UpdateGlobal, VisualTestContext,
   21    WindowBounds, WindowOptions, div,
   22};
   23use indoc::indoc;
   24use language::{
   25    BracketPairConfig,
   26    Capability::ReadWrite,
   27    DiagnosticSourceKind, FakeLspAdapter, IndentGuideSettings, LanguageConfig,
   28    LanguageConfigOverride, LanguageMatcher, LanguageName, Override, Point,
   29    language_settings::{
   30        CompletionSettingsContent, FormatterList, LanguageSettingsContent, LspInsertMode,
   31    },
   32    tree_sitter_python,
   33};
   34use language_settings::Formatter;
   35use languages::markdown_lang;
   36use languages::rust_lang;
   37use lsp::CompletionParams;
   38use multi_buffer::{
   39    ExcerptRange, IndentGuide, MultiBuffer, MultiBufferOffset, MultiBufferOffsetUtf16, PathKey,
   40};
   41use parking_lot::Mutex;
   42use pretty_assertions::{assert_eq, assert_ne};
   43use project::{
   44    FakeFs, Project,
   45    debugger::breakpoint_store::{BreakpointState, SourceBreakpoint},
   46    project_settings::LspSettings,
   47    trusted_worktrees::{PathTrust, TrustedWorktrees},
   48};
   49use serde_json::{self, json};
   50use settings::{
   51    AllLanguageSettingsContent, DelayMs, EditorSettingsContent, IndentGuideBackgroundColoring,
   52    IndentGuideColoring, InlayHintSettingsContent, ProjectSettingsContent, SearchSettingsContent,
   53    SettingsStore,
   54};
   55use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant};
   56use std::{
   57    iter,
   58    sync::atomic::{self, AtomicUsize},
   59};
   60use test::build_editor_with_project;
   61use text::ToPoint as _;
   62use unindent::Unindent;
   63use util::{
   64    assert_set_eq, path,
   65    rel_path::rel_path,
   66    test::{TextRangeMarker, marked_text_ranges, marked_text_ranges_by, sample_text},
   67    uri,
   68};
   69use workspace::{
   70    CloseActiveItem, CloseAllItems, CloseOtherItems, MoveItemToPaneInDirection, NavigationEntry,
   71    OpenOptions, ViewId,
   72    item::{FollowEvent, FollowableItem, Item, ItemHandle, SaveOptions},
   73    register_project_item,
   74};
   75
   76fn display_ranges(editor: &Editor, cx: &mut Context<'_, Editor>) -> Vec<Range<DisplayPoint>> {
   77    editor
   78        .selections
   79        .display_ranges(&editor.display_snapshot(cx))
   80}
   81
   82#[gpui::test]
   83fn test_edit_events(cx: &mut TestAppContext) {
   84    init_test(cx, |_| {});
   85
   86    let buffer = cx.new(|cx| {
   87        let mut buffer = language::Buffer::local("123456", cx);
   88        buffer.set_group_interval(Duration::from_secs(1));
   89        buffer
   90    });
   91
   92    let events = Rc::new(RefCell::new(Vec::new()));
   93    let editor1 = cx.add_window({
   94        let events = events.clone();
   95        |window, cx| {
   96            let entity = cx.entity();
   97            cx.subscribe_in(
   98                &entity,
   99                window,
  100                move |_, _, event: &EditorEvent, _, _| match event {
  101                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
  102                    EditorEvent::BufferEdited => {
  103                        events.borrow_mut().push(("editor1", "buffer edited"))
  104                    }
  105                    _ => {}
  106                },
  107            )
  108            .detach();
  109            Editor::for_buffer(buffer.clone(), None, window, cx)
  110        }
  111    });
  112
  113    let editor2 = cx.add_window({
  114        let events = events.clone();
  115        |window, cx| {
  116            cx.subscribe_in(
  117                &cx.entity(),
  118                window,
  119                move |_, _, event: &EditorEvent, _, _| match event {
  120                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
  121                    EditorEvent::BufferEdited => {
  122                        events.borrow_mut().push(("editor2", "buffer edited"))
  123                    }
  124                    _ => {}
  125                },
  126            )
  127            .detach();
  128            Editor::for_buffer(buffer.clone(), None, window, cx)
  129        }
  130    });
  131
  132    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  133
  134    // Mutating editor 1 will emit an `Edited` event only for that editor.
  135    _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
  136    assert_eq!(
  137        mem::take(&mut *events.borrow_mut()),
  138        [
  139            ("editor1", "edited"),
  140            ("editor1", "buffer edited"),
  141            ("editor2", "buffer edited"),
  142        ]
  143    );
  144
  145    // Mutating editor 2 will emit an `Edited` event only for that editor.
  146    _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
  147    assert_eq!(
  148        mem::take(&mut *events.borrow_mut()),
  149        [
  150            ("editor2", "edited"),
  151            ("editor1", "buffer edited"),
  152            ("editor2", "buffer edited"),
  153        ]
  154    );
  155
  156    // Undoing on editor 1 will emit an `Edited` event only for that editor.
  157    _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  158    assert_eq!(
  159        mem::take(&mut *events.borrow_mut()),
  160        [
  161            ("editor1", "edited"),
  162            ("editor1", "buffer edited"),
  163            ("editor2", "buffer edited"),
  164        ]
  165    );
  166
  167    // Redoing on editor 1 will emit an `Edited` event only for that editor.
  168    _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  169    assert_eq!(
  170        mem::take(&mut *events.borrow_mut()),
  171        [
  172            ("editor1", "edited"),
  173            ("editor1", "buffer edited"),
  174            ("editor2", "buffer edited"),
  175        ]
  176    );
  177
  178    // Undoing on editor 2 will emit an `Edited` event only for that editor.
  179    _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  180    assert_eq!(
  181        mem::take(&mut *events.borrow_mut()),
  182        [
  183            ("editor2", "edited"),
  184            ("editor1", "buffer edited"),
  185            ("editor2", "buffer edited"),
  186        ]
  187    );
  188
  189    // Redoing on editor 2 will emit an `Edited` event only for that editor.
  190    _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  191    assert_eq!(
  192        mem::take(&mut *events.borrow_mut()),
  193        [
  194            ("editor2", "edited"),
  195            ("editor1", "buffer edited"),
  196            ("editor2", "buffer edited"),
  197        ]
  198    );
  199
  200    // No event is emitted when the mutation is a no-op.
  201    _ = editor2.update(cx, |editor, window, cx| {
  202        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  203            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
  204        });
  205
  206        editor.backspace(&Backspace, window, cx);
  207    });
  208    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  209}
  210
  211#[gpui::test]
  212fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
  213    init_test(cx, |_| {});
  214
  215    let mut now = Instant::now();
  216    let group_interval = Duration::from_millis(1);
  217    let buffer = cx.new(|cx| {
  218        let mut buf = language::Buffer::local("123456", cx);
  219        buf.set_group_interval(group_interval);
  220        buf
  221    });
  222    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  223    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
  224
  225    _ = editor.update(cx, |editor, window, cx| {
  226        editor.start_transaction_at(now, window, cx);
  227        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  228            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(4)])
  229        });
  230
  231        editor.insert("cd", window, cx);
  232        editor.end_transaction_at(now, cx);
  233        assert_eq!(editor.text(cx), "12cd56");
  234        assert_eq!(
  235            editor.selections.ranges(&editor.display_snapshot(cx)),
  236            vec![MultiBufferOffset(4)..MultiBufferOffset(4)]
  237        );
  238
  239        editor.start_transaction_at(now, window, cx);
  240        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  241            s.select_ranges([MultiBufferOffset(4)..MultiBufferOffset(5)])
  242        });
  243        editor.insert("e", window, cx);
  244        editor.end_transaction_at(now, cx);
  245        assert_eq!(editor.text(cx), "12cde6");
  246        assert_eq!(
  247            editor.selections.ranges(&editor.display_snapshot(cx)),
  248            vec![MultiBufferOffset(5)..MultiBufferOffset(5)]
  249        );
  250
  251        now += group_interval + Duration::from_millis(1);
  252        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  253            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(2)])
  254        });
  255
  256        // Simulate an edit in another editor
  257        buffer.update(cx, |buffer, cx| {
  258            buffer.start_transaction_at(now, cx);
  259            buffer.edit(
  260                [(MultiBufferOffset(0)..MultiBufferOffset(1), "a")],
  261                None,
  262                cx,
  263            );
  264            buffer.edit(
  265                [(MultiBufferOffset(1)..MultiBufferOffset(1), "b")],
  266                None,
  267                cx,
  268            );
  269            buffer.end_transaction_at(now, cx);
  270        });
  271
  272        assert_eq!(editor.text(cx), "ab2cde6");
  273        assert_eq!(
  274            editor.selections.ranges(&editor.display_snapshot(cx)),
  275            vec![MultiBufferOffset(3)..MultiBufferOffset(3)]
  276        );
  277
  278        // Last transaction happened past the group interval in a different editor.
  279        // Undo it individually and don't restore selections.
  280        editor.undo(&Undo, window, cx);
  281        assert_eq!(editor.text(cx), "12cde6");
  282        assert_eq!(
  283            editor.selections.ranges(&editor.display_snapshot(cx)),
  284            vec![MultiBufferOffset(2)..MultiBufferOffset(2)]
  285        );
  286
  287        // First two transactions happened within the group interval in this editor.
  288        // Undo them together and restore selections.
  289        editor.undo(&Undo, window, cx);
  290        editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
  291        assert_eq!(editor.text(cx), "123456");
  292        assert_eq!(
  293            editor.selections.ranges(&editor.display_snapshot(cx)),
  294            vec![MultiBufferOffset(0)..MultiBufferOffset(0)]
  295        );
  296
  297        // Redo the first two transactions together.
  298        editor.redo(&Redo, window, cx);
  299        assert_eq!(editor.text(cx), "12cde6");
  300        assert_eq!(
  301            editor.selections.ranges(&editor.display_snapshot(cx)),
  302            vec![MultiBufferOffset(5)..MultiBufferOffset(5)]
  303        );
  304
  305        // Redo the last transaction on its own.
  306        editor.redo(&Redo, window, cx);
  307        assert_eq!(editor.text(cx), "ab2cde6");
  308        assert_eq!(
  309            editor.selections.ranges(&editor.display_snapshot(cx)),
  310            vec![MultiBufferOffset(6)..MultiBufferOffset(6)]
  311        );
  312
  313        // Test empty transactions.
  314        editor.start_transaction_at(now, window, cx);
  315        editor.end_transaction_at(now, cx);
  316        editor.undo(&Undo, window, cx);
  317        assert_eq!(editor.text(cx), "12cde6");
  318    });
  319}
  320
  321#[gpui::test]
  322fn test_ime_composition(cx: &mut TestAppContext) {
  323    init_test(cx, |_| {});
  324
  325    let buffer = cx.new(|cx| {
  326        let mut buffer = language::Buffer::local("abcde", cx);
  327        // Ensure automatic grouping doesn't occur.
  328        buffer.set_group_interval(Duration::ZERO);
  329        buffer
  330    });
  331
  332    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  333    cx.add_window(|window, cx| {
  334        let mut editor = build_editor(buffer.clone(), window, cx);
  335
  336        // Start a new IME composition.
  337        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  338        editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx);
  339        editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
  340        assert_eq!(editor.text(cx), "äbcde");
  341        assert_eq!(
  342            editor.marked_text_ranges(cx),
  343            Some(vec![
  344                MultiBufferOffsetUtf16(OffsetUtf16(0))..MultiBufferOffsetUtf16(OffsetUtf16(1))
  345            ])
  346        );
  347
  348        // Finalize IME composition.
  349        editor.replace_text_in_range(None, "ā", window, cx);
  350        assert_eq!(editor.text(cx), "ābcde");
  351        assert_eq!(editor.marked_text_ranges(cx), None);
  352
  353        // IME composition edits are grouped and are undone/redone at once.
  354        editor.undo(&Default::default(), window, cx);
  355        assert_eq!(editor.text(cx), "abcde");
  356        assert_eq!(editor.marked_text_ranges(cx), None);
  357        editor.redo(&Default::default(), window, cx);
  358        assert_eq!(editor.text(cx), "ābcde");
  359        assert_eq!(editor.marked_text_ranges(cx), None);
  360
  361        // Start a new IME composition.
  362        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  363        assert_eq!(
  364            editor.marked_text_ranges(cx),
  365            Some(vec![
  366                MultiBufferOffsetUtf16(OffsetUtf16(0))..MultiBufferOffsetUtf16(OffsetUtf16(1))
  367            ])
  368        );
  369
  370        // Undoing during an IME composition cancels it.
  371        editor.undo(&Default::default(), window, cx);
  372        assert_eq!(editor.text(cx), "ābcde");
  373        assert_eq!(editor.marked_text_ranges(cx), None);
  374
  375        // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
  376        editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
  377        assert_eq!(editor.text(cx), "ābcdè");
  378        assert_eq!(
  379            editor.marked_text_ranges(cx),
  380            Some(vec![
  381                MultiBufferOffsetUtf16(OffsetUtf16(4))..MultiBufferOffsetUtf16(OffsetUtf16(5))
  382            ])
  383        );
  384
  385        // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
  386        editor.replace_text_in_range(Some(4..999), "ę", window, cx);
  387        assert_eq!(editor.text(cx), "ābcdę");
  388        assert_eq!(editor.marked_text_ranges(cx), None);
  389
  390        // Start a new IME composition with multiple cursors.
  391        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  392            s.select_ranges([
  393                MultiBufferOffsetUtf16(OffsetUtf16(1))..MultiBufferOffsetUtf16(OffsetUtf16(1)),
  394                MultiBufferOffsetUtf16(OffsetUtf16(3))..MultiBufferOffsetUtf16(OffsetUtf16(3)),
  395                MultiBufferOffsetUtf16(OffsetUtf16(5))..MultiBufferOffsetUtf16(OffsetUtf16(5)),
  396            ])
  397        });
  398        editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
  399        assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
  400        assert_eq!(
  401            editor.marked_text_ranges(cx),
  402            Some(vec![
  403                MultiBufferOffsetUtf16(OffsetUtf16(0))..MultiBufferOffsetUtf16(OffsetUtf16(3)),
  404                MultiBufferOffsetUtf16(OffsetUtf16(4))..MultiBufferOffsetUtf16(OffsetUtf16(7)),
  405                MultiBufferOffsetUtf16(OffsetUtf16(8))..MultiBufferOffsetUtf16(OffsetUtf16(11))
  406            ])
  407        );
  408
  409        // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
  410        editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
  411        assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
  412        assert_eq!(
  413            editor.marked_text_ranges(cx),
  414            Some(vec![
  415                MultiBufferOffsetUtf16(OffsetUtf16(1))..MultiBufferOffsetUtf16(OffsetUtf16(2)),
  416                MultiBufferOffsetUtf16(OffsetUtf16(5))..MultiBufferOffsetUtf16(OffsetUtf16(6)),
  417                MultiBufferOffsetUtf16(OffsetUtf16(9))..MultiBufferOffsetUtf16(OffsetUtf16(10))
  418            ])
  419        );
  420
  421        // Finalize IME composition with multiple cursors.
  422        editor.replace_text_in_range(Some(9..10), "2", window, cx);
  423        assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
  424        assert_eq!(editor.marked_text_ranges(cx), None);
  425
  426        editor
  427    });
  428}
  429
  430#[gpui::test]
  431fn test_selection_with_mouse(cx: &mut TestAppContext) {
  432    init_test(cx, |_| {});
  433
  434    let editor = cx.add_window(|window, cx| {
  435        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  436        build_editor(buffer, window, cx)
  437    });
  438
  439    _ = editor.update(cx, |editor, window, cx| {
  440        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  441    });
  442    assert_eq!(
  443        editor
  444            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  445            .unwrap(),
  446        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  447    );
  448
  449    _ = editor.update(cx, |editor, window, cx| {
  450        editor.update_selection(
  451            DisplayPoint::new(DisplayRow(3), 3),
  452            0,
  453            gpui::Point::<f32>::default(),
  454            window,
  455            cx,
  456        );
  457    });
  458
  459    assert_eq!(
  460        editor
  461            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  462            .unwrap(),
  463        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  464    );
  465
  466    _ = editor.update(cx, |editor, window, cx| {
  467        editor.update_selection(
  468            DisplayPoint::new(DisplayRow(1), 1),
  469            0,
  470            gpui::Point::<f32>::default(),
  471            window,
  472            cx,
  473        );
  474    });
  475
  476    assert_eq!(
  477        editor
  478            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  479            .unwrap(),
  480        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  481    );
  482
  483    _ = editor.update(cx, |editor, window, cx| {
  484        editor.end_selection(window, cx);
  485        editor.update_selection(
  486            DisplayPoint::new(DisplayRow(3), 3),
  487            0,
  488            gpui::Point::<f32>::default(),
  489            window,
  490            cx,
  491        );
  492    });
  493
  494    assert_eq!(
  495        editor
  496            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  497            .unwrap(),
  498        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  499    );
  500
  501    _ = editor.update(cx, |editor, window, cx| {
  502        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
  503        editor.update_selection(
  504            DisplayPoint::new(DisplayRow(0), 0),
  505            0,
  506            gpui::Point::<f32>::default(),
  507            window,
  508            cx,
  509        );
  510    });
  511
  512    assert_eq!(
  513        editor
  514            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  515            .unwrap(),
  516        [
  517            DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
  518            DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
  519        ]
  520    );
  521
  522    _ = editor.update(cx, |editor, window, cx| {
  523        editor.end_selection(window, cx);
  524    });
  525
  526    assert_eq!(
  527        editor
  528            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  529            .unwrap(),
  530        [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
  531    );
  532}
  533
  534#[gpui::test]
  535fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
  536    init_test(cx, |_| {});
  537
  538    let editor = cx.add_window(|window, cx| {
  539        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  540        build_editor(buffer, window, cx)
  541    });
  542
  543    _ = editor.update(cx, |editor, window, cx| {
  544        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
  545    });
  546
  547    _ = editor.update(cx, |editor, window, cx| {
  548        editor.end_selection(window, cx);
  549    });
  550
  551    _ = editor.update(cx, |editor, window, cx| {
  552        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
  553    });
  554
  555    _ = editor.update(cx, |editor, window, cx| {
  556        editor.end_selection(window, cx);
  557    });
  558
  559    assert_eq!(
  560        editor
  561            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  562            .unwrap(),
  563        [
  564            DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
  565            DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
  566        ]
  567    );
  568
  569    _ = editor.update(cx, |editor, window, cx| {
  570        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
  571    });
  572
  573    _ = editor.update(cx, |editor, window, cx| {
  574        editor.end_selection(window, cx);
  575    });
  576
  577    assert_eq!(
  578        editor
  579            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  580            .unwrap(),
  581        [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  582    );
  583}
  584
  585#[gpui::test]
  586fn test_canceling_pending_selection(cx: &mut TestAppContext) {
  587    init_test(cx, |_| {});
  588
  589    let editor = cx.add_window(|window, cx| {
  590        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  591        build_editor(buffer, window, cx)
  592    });
  593
  594    _ = editor.update(cx, |editor, window, cx| {
  595        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  596        assert_eq!(
  597            display_ranges(editor, cx),
  598            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  599        );
  600    });
  601
  602    _ = editor.update(cx, |editor, window, cx| {
  603        editor.update_selection(
  604            DisplayPoint::new(DisplayRow(3), 3),
  605            0,
  606            gpui::Point::<f32>::default(),
  607            window,
  608            cx,
  609        );
  610        assert_eq!(
  611            display_ranges(editor, cx),
  612            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  613        );
  614    });
  615
  616    _ = editor.update(cx, |editor, window, cx| {
  617        editor.cancel(&Cancel, window, cx);
  618        editor.update_selection(
  619            DisplayPoint::new(DisplayRow(1), 1),
  620            0,
  621            gpui::Point::<f32>::default(),
  622            window,
  623            cx,
  624        );
  625        assert_eq!(
  626            display_ranges(editor, cx),
  627            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  628        );
  629    });
  630}
  631
  632#[gpui::test]
  633fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
  634    init_test(cx, |_| {});
  635
  636    let editor = cx.add_window(|window, cx| {
  637        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  638        build_editor(buffer, window, cx)
  639    });
  640
  641    _ = editor.update(cx, |editor, window, cx| {
  642        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  643        assert_eq!(
  644            display_ranges(editor, cx),
  645            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  646        );
  647
  648        editor.move_down(&Default::default(), window, cx);
  649        assert_eq!(
  650            display_ranges(editor, cx),
  651            [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  652        );
  653
  654        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  655        assert_eq!(
  656            display_ranges(editor, cx),
  657            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  658        );
  659
  660        editor.move_up(&Default::default(), window, cx);
  661        assert_eq!(
  662            display_ranges(editor, cx),
  663            [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
  664        );
  665    });
  666}
  667
  668#[gpui::test]
  669fn test_extending_selection(cx: &mut TestAppContext) {
  670    init_test(cx, |_| {});
  671
  672    let editor = cx.add_window(|window, cx| {
  673        let buffer = MultiBuffer::build_simple("aaa bbb ccc ddd eee", cx);
  674        build_editor(buffer, window, cx)
  675    });
  676
  677    _ = editor.update(cx, |editor, window, cx| {
  678        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), false, 1, window, cx);
  679        editor.end_selection(window, cx);
  680        assert_eq!(
  681            display_ranges(editor, cx),
  682            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)]
  683        );
  684
  685        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  686        editor.end_selection(window, cx);
  687        assert_eq!(
  688            display_ranges(editor, cx),
  689            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 10)]
  690        );
  691
  692        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  693        editor.end_selection(window, cx);
  694        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 2, window, cx);
  695        assert_eq!(
  696            display_ranges(editor, cx),
  697            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 11)]
  698        );
  699
  700        editor.update_selection(
  701            DisplayPoint::new(DisplayRow(0), 1),
  702            0,
  703            gpui::Point::<f32>::default(),
  704            window,
  705            cx,
  706        );
  707        editor.end_selection(window, cx);
  708        assert_eq!(
  709            display_ranges(editor, cx),
  710            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 0)]
  711        );
  712
  713        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), true, 1, window, cx);
  714        editor.end_selection(window, cx);
  715        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), true, 2, window, cx);
  716        editor.end_selection(window, cx);
  717        assert_eq!(
  718            display_ranges(editor, cx),
  719            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 7)]
  720        );
  721
  722        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  723        assert_eq!(
  724            display_ranges(editor, cx),
  725            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 11)]
  726        );
  727
  728        editor.update_selection(
  729            DisplayPoint::new(DisplayRow(0), 6),
  730            0,
  731            gpui::Point::<f32>::default(),
  732            window,
  733            cx,
  734        );
  735        assert_eq!(
  736            display_ranges(editor, cx),
  737            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 7)]
  738        );
  739
  740        editor.update_selection(
  741            DisplayPoint::new(DisplayRow(0), 1),
  742            0,
  743            gpui::Point::<f32>::default(),
  744            window,
  745            cx,
  746        );
  747        editor.end_selection(window, cx);
  748        assert_eq!(
  749            display_ranges(editor, cx),
  750            [DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 0)]
  751        );
  752    });
  753}
  754
  755#[gpui::test]
  756fn test_clone(cx: &mut TestAppContext) {
  757    init_test(cx, |_| {});
  758
  759    let (text, selection_ranges) = marked_text_ranges(
  760        indoc! {"
  761            one
  762            two
  763            threeˇ
  764            four
  765            fiveˇ
  766        "},
  767        true,
  768    );
  769
  770    let editor = cx.add_window(|window, cx| {
  771        let buffer = MultiBuffer::build_simple(&text, cx);
  772        build_editor(buffer, window, cx)
  773    });
  774
  775    _ = editor.update(cx, |editor, window, cx| {
  776        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  777            s.select_ranges(
  778                selection_ranges
  779                    .iter()
  780                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end)),
  781            )
  782        });
  783        editor.fold_creases(
  784            vec![
  785                Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
  786                Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
  787            ],
  788            true,
  789            window,
  790            cx,
  791        );
  792    });
  793
  794    let cloned_editor = editor
  795        .update(cx, |editor, _, cx| {
  796            cx.open_window(Default::default(), |window, cx| {
  797                cx.new(|cx| editor.clone(window, cx))
  798            })
  799        })
  800        .unwrap()
  801        .unwrap();
  802
  803    let snapshot = editor
  804        .update(cx, |e, window, cx| e.snapshot(window, cx))
  805        .unwrap();
  806    let cloned_snapshot = cloned_editor
  807        .update(cx, |e, window, cx| e.snapshot(window, cx))
  808        .unwrap();
  809
  810    assert_eq!(
  811        cloned_editor
  812            .update(cx, |e, _, cx| e.display_text(cx))
  813            .unwrap(),
  814        editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
  815    );
  816    assert_eq!(
  817        cloned_snapshot
  818            .folds_in_range(MultiBufferOffset(0)..MultiBufferOffset(text.len()))
  819            .collect::<Vec<_>>(),
  820        snapshot
  821            .folds_in_range(MultiBufferOffset(0)..MultiBufferOffset(text.len()))
  822            .collect::<Vec<_>>(),
  823    );
  824    assert_set_eq!(
  825        cloned_editor
  826            .update(cx, |editor, _, cx| editor
  827                .selections
  828                .ranges::<Point>(&editor.display_snapshot(cx)))
  829            .unwrap(),
  830        editor
  831            .update(cx, |editor, _, cx| editor
  832                .selections
  833                .ranges(&editor.display_snapshot(cx)))
  834            .unwrap()
  835    );
  836    assert_set_eq!(
  837        cloned_editor
  838            .update(cx, |e, _window, cx| e
  839                .selections
  840                .display_ranges(&e.display_snapshot(cx)))
  841            .unwrap(),
  842        editor
  843            .update(cx, |e, _, cx| e
  844                .selections
  845                .display_ranges(&e.display_snapshot(cx)))
  846            .unwrap()
  847    );
  848}
  849
  850#[gpui::test]
  851async fn test_navigation_history(cx: &mut TestAppContext) {
  852    init_test(cx, |_| {});
  853
  854    use workspace::item::Item;
  855
  856    let fs = FakeFs::new(cx.executor());
  857    let project = Project::test(fs, [], cx).await;
  858    let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
  859    let pane = workspace
  860        .update(cx, |workspace, _, _| workspace.active_pane().clone())
  861        .unwrap();
  862
  863    _ = workspace.update(cx, |_v, window, cx| {
  864        cx.new(|cx| {
  865            let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
  866            let mut editor = build_editor(buffer, window, cx);
  867            let handle = cx.entity();
  868            editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
  869
  870            fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
  871                editor.nav_history.as_mut().unwrap().pop_backward(cx)
  872            }
  873
  874            // Move the cursor a small distance.
  875            // Nothing is added to the navigation history.
  876            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  877                s.select_display_ranges([
  878                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
  879                ])
  880            });
  881            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  882                s.select_display_ranges([
  883                    DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
  884                ])
  885            });
  886            assert!(pop_history(&mut editor, cx).is_none());
  887
  888            // Move the cursor a large distance.
  889            // The history can jump back to the previous position.
  890            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  891                s.select_display_ranges([
  892                    DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
  893                ])
  894            });
  895            let nav_entry = pop_history(&mut editor, cx).unwrap();
  896            editor.navigate(nav_entry.data.unwrap(), window, cx);
  897            assert_eq!(nav_entry.item.id(), cx.entity_id());
  898            assert_eq!(
  899                editor
  900                    .selections
  901                    .display_ranges(&editor.display_snapshot(cx)),
  902                &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
  903            );
  904            assert!(pop_history(&mut editor, cx).is_none());
  905
  906            // Move the cursor a small distance via the mouse.
  907            // Nothing is added to the navigation history.
  908            editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
  909            editor.end_selection(window, cx);
  910            assert_eq!(
  911                editor
  912                    .selections
  913                    .display_ranges(&editor.display_snapshot(cx)),
  914                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  915            );
  916            assert!(pop_history(&mut editor, cx).is_none());
  917
  918            // Move the cursor a large distance via the mouse.
  919            // The history can jump back to the previous position.
  920            editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
  921            editor.end_selection(window, cx);
  922            assert_eq!(
  923                editor
  924                    .selections
  925                    .display_ranges(&editor.display_snapshot(cx)),
  926                &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
  927            );
  928            let nav_entry = pop_history(&mut editor, cx).unwrap();
  929            editor.navigate(nav_entry.data.unwrap(), window, cx);
  930            assert_eq!(nav_entry.item.id(), cx.entity_id());
  931            assert_eq!(
  932                editor
  933                    .selections
  934                    .display_ranges(&editor.display_snapshot(cx)),
  935                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  936            );
  937            assert!(pop_history(&mut editor, cx).is_none());
  938
  939            // Set scroll position to check later
  940            editor.set_scroll_position(gpui::Point::<f64>::new(5.5, 5.5), window, cx);
  941            let original_scroll_position = editor.scroll_manager.anchor();
  942
  943            // Jump to the end of the document and adjust scroll
  944            editor.move_to_end(&MoveToEnd, window, cx);
  945            editor.set_scroll_position(gpui::Point::<f64>::new(-2.5, -0.5), window, cx);
  946            assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
  947
  948            let nav_entry = pop_history(&mut editor, cx).unwrap();
  949            editor.navigate(nav_entry.data.unwrap(), window, cx);
  950            assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
  951
  952            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
  953            let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
  954            invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
  955            let invalid_point = Point::new(9999, 0);
  956            editor.navigate(
  957                Arc::new(NavigationData {
  958                    cursor_anchor: invalid_anchor,
  959                    cursor_position: invalid_point,
  960                    scroll_anchor: ScrollAnchor {
  961                        anchor: invalid_anchor,
  962                        offset: Default::default(),
  963                    },
  964                    scroll_top_row: invalid_point.row,
  965                }),
  966                window,
  967                cx,
  968            );
  969            assert_eq!(
  970                editor
  971                    .selections
  972                    .display_ranges(&editor.display_snapshot(cx)),
  973                &[editor.max_point(cx)..editor.max_point(cx)]
  974            );
  975            assert_eq!(
  976                editor.scroll_position(cx),
  977                gpui::Point::new(0., editor.max_point(cx).row().as_f64())
  978            );
  979
  980            editor
  981        })
  982    });
  983}
  984
  985#[gpui::test]
  986fn test_cancel(cx: &mut TestAppContext) {
  987    init_test(cx, |_| {});
  988
  989    let editor = cx.add_window(|window, cx| {
  990        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  991        build_editor(buffer, window, cx)
  992    });
  993
  994    _ = editor.update(cx, |editor, window, cx| {
  995        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
  996        editor.update_selection(
  997            DisplayPoint::new(DisplayRow(1), 1),
  998            0,
  999            gpui::Point::<f32>::default(),
 1000            window,
 1001            cx,
 1002        );
 1003        editor.end_selection(window, cx);
 1004
 1005        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
 1006        editor.update_selection(
 1007            DisplayPoint::new(DisplayRow(0), 3),
 1008            0,
 1009            gpui::Point::<f32>::default(),
 1010            window,
 1011            cx,
 1012        );
 1013        editor.end_selection(window, cx);
 1014        assert_eq!(
 1015            display_ranges(editor, cx),
 1016            [
 1017                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
 1018                DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
 1019            ]
 1020        );
 1021    });
 1022
 1023    _ = editor.update(cx, |editor, window, cx| {
 1024        editor.cancel(&Cancel, window, cx);
 1025        assert_eq!(
 1026            display_ranges(editor, cx),
 1027            [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
 1028        );
 1029    });
 1030
 1031    _ = editor.update(cx, |editor, window, cx| {
 1032        editor.cancel(&Cancel, window, cx);
 1033        assert_eq!(
 1034            display_ranges(editor, cx),
 1035            [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
 1036        );
 1037    });
 1038}
 1039
 1040#[gpui::test]
 1041fn test_fold_action(cx: &mut TestAppContext) {
 1042    init_test(cx, |_| {});
 1043
 1044    let editor = cx.add_window(|window, cx| {
 1045        let buffer = MultiBuffer::build_simple(
 1046            &"
 1047                impl Foo {
 1048                    // Hello!
 1049
 1050                    fn a() {
 1051                        1
 1052                    }
 1053
 1054                    fn b() {
 1055                        2
 1056                    }
 1057
 1058                    fn c() {
 1059                        3
 1060                    }
 1061                }
 1062            "
 1063            .unindent(),
 1064            cx,
 1065        );
 1066        build_editor(buffer, window, cx)
 1067    });
 1068
 1069    _ = editor.update(cx, |editor, window, cx| {
 1070        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1071            s.select_display_ranges([
 1072                DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
 1073            ]);
 1074        });
 1075        editor.fold(&Fold, window, cx);
 1076        assert_eq!(
 1077            editor.display_text(cx),
 1078            "
 1079                impl Foo {
 1080                    // Hello!
 1081
 1082                    fn a() {
 1083                        1
 1084                    }
 1085
 1086                    fn b() {⋯
 1087                    }
 1088
 1089                    fn c() {⋯
 1090                    }
 1091                }
 1092            "
 1093            .unindent(),
 1094        );
 1095
 1096        editor.fold(&Fold, window, cx);
 1097        assert_eq!(
 1098            editor.display_text(cx),
 1099            "
 1100                impl Foo {⋯
 1101                }
 1102            "
 1103            .unindent(),
 1104        );
 1105
 1106        editor.unfold_lines(&UnfoldLines, window, cx);
 1107        assert_eq!(
 1108            editor.display_text(cx),
 1109            "
 1110                impl Foo {
 1111                    // Hello!
 1112
 1113                    fn a() {
 1114                        1
 1115                    }
 1116
 1117                    fn b() {⋯
 1118                    }
 1119
 1120                    fn c() {⋯
 1121                    }
 1122                }
 1123            "
 1124            .unindent(),
 1125        );
 1126
 1127        editor.unfold_lines(&UnfoldLines, window, cx);
 1128        assert_eq!(
 1129            editor.display_text(cx),
 1130            editor.buffer.read(cx).read(cx).text()
 1131        );
 1132    });
 1133}
 1134
 1135#[gpui::test]
 1136fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
 1137    init_test(cx, |_| {});
 1138
 1139    let editor = cx.add_window(|window, cx| {
 1140        let buffer = MultiBuffer::build_simple(
 1141            &"
 1142                class Foo:
 1143                    # Hello!
 1144
 1145                    def a():
 1146                        print(1)
 1147
 1148                    def b():
 1149                        print(2)
 1150
 1151                    def c():
 1152                        print(3)
 1153            "
 1154            .unindent(),
 1155            cx,
 1156        );
 1157        build_editor(buffer, window, cx)
 1158    });
 1159
 1160    _ = editor.update(cx, |editor, window, cx| {
 1161        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1162            s.select_display_ranges([
 1163                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
 1164            ]);
 1165        });
 1166        editor.fold(&Fold, window, cx);
 1167        assert_eq!(
 1168            editor.display_text(cx),
 1169            "
 1170                class Foo:
 1171                    # Hello!
 1172
 1173                    def a():
 1174                        print(1)
 1175
 1176                    def b():⋯
 1177
 1178                    def c():⋯
 1179            "
 1180            .unindent(),
 1181        );
 1182
 1183        editor.fold(&Fold, window, cx);
 1184        assert_eq!(
 1185            editor.display_text(cx),
 1186            "
 1187                class Foo:⋯
 1188            "
 1189            .unindent(),
 1190        );
 1191
 1192        editor.unfold_lines(&UnfoldLines, window, cx);
 1193        assert_eq!(
 1194            editor.display_text(cx),
 1195            "
 1196                class Foo:
 1197                    # Hello!
 1198
 1199                    def a():
 1200                        print(1)
 1201
 1202                    def b():⋯
 1203
 1204                    def c():⋯
 1205            "
 1206            .unindent(),
 1207        );
 1208
 1209        editor.unfold_lines(&UnfoldLines, window, cx);
 1210        assert_eq!(
 1211            editor.display_text(cx),
 1212            editor.buffer.read(cx).read(cx).text()
 1213        );
 1214    });
 1215}
 1216
 1217#[gpui::test]
 1218fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
 1219    init_test(cx, |_| {});
 1220
 1221    let editor = cx.add_window(|window, cx| {
 1222        let buffer = MultiBuffer::build_simple(
 1223            &"
 1224                class Foo:
 1225                    # Hello!
 1226
 1227                    def a():
 1228                        print(1)
 1229
 1230                    def b():
 1231                        print(2)
 1232
 1233
 1234                    def c():
 1235                        print(3)
 1236
 1237
 1238            "
 1239            .unindent(),
 1240            cx,
 1241        );
 1242        build_editor(buffer, window, cx)
 1243    });
 1244
 1245    _ = editor.update(cx, |editor, window, cx| {
 1246        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1247            s.select_display_ranges([
 1248                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
 1249            ]);
 1250        });
 1251        editor.fold(&Fold, window, cx);
 1252        assert_eq!(
 1253            editor.display_text(cx),
 1254            "
 1255                class Foo:
 1256                    # Hello!
 1257
 1258                    def a():
 1259                        print(1)
 1260
 1261                    def b():⋯
 1262
 1263
 1264                    def c():⋯
 1265
 1266
 1267            "
 1268            .unindent(),
 1269        );
 1270
 1271        editor.fold(&Fold, window, cx);
 1272        assert_eq!(
 1273            editor.display_text(cx),
 1274            "
 1275                class Foo:⋯
 1276
 1277
 1278            "
 1279            .unindent(),
 1280        );
 1281
 1282        editor.unfold_lines(&UnfoldLines, window, cx);
 1283        assert_eq!(
 1284            editor.display_text(cx),
 1285            "
 1286                class Foo:
 1287                    # Hello!
 1288
 1289                    def a():
 1290                        print(1)
 1291
 1292                    def b():⋯
 1293
 1294
 1295                    def c():⋯
 1296
 1297
 1298            "
 1299            .unindent(),
 1300        );
 1301
 1302        editor.unfold_lines(&UnfoldLines, window, cx);
 1303        assert_eq!(
 1304            editor.display_text(cx),
 1305            editor.buffer.read(cx).read(cx).text()
 1306        );
 1307    });
 1308}
 1309
 1310#[gpui::test]
 1311fn test_fold_at_level(cx: &mut TestAppContext) {
 1312    init_test(cx, |_| {});
 1313
 1314    let editor = cx.add_window(|window, cx| {
 1315        let buffer = MultiBuffer::build_simple(
 1316            &"
 1317                class Foo:
 1318                    # Hello!
 1319
 1320                    def a():
 1321                        print(1)
 1322
 1323                    def b():
 1324                        print(2)
 1325
 1326
 1327                class Bar:
 1328                    # World!
 1329
 1330                    def a():
 1331                        print(1)
 1332
 1333                    def b():
 1334                        print(2)
 1335
 1336
 1337            "
 1338            .unindent(),
 1339            cx,
 1340        );
 1341        build_editor(buffer, window, cx)
 1342    });
 1343
 1344    _ = editor.update(cx, |editor, window, cx| {
 1345        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1346        assert_eq!(
 1347            editor.display_text(cx),
 1348            "
 1349                class Foo:
 1350                    # Hello!
 1351
 1352                    def a():⋯
 1353
 1354                    def b():⋯
 1355
 1356
 1357                class Bar:
 1358                    # World!
 1359
 1360                    def a():⋯
 1361
 1362                    def b():⋯
 1363
 1364
 1365            "
 1366            .unindent(),
 1367        );
 1368
 1369        editor.fold_at_level(&FoldAtLevel(1), window, cx);
 1370        assert_eq!(
 1371            editor.display_text(cx),
 1372            "
 1373                class Foo:⋯
 1374
 1375
 1376                class Bar:⋯
 1377
 1378
 1379            "
 1380            .unindent(),
 1381        );
 1382
 1383        editor.unfold_all(&UnfoldAll, window, cx);
 1384        editor.fold_at_level(&FoldAtLevel(0), window, cx);
 1385        assert_eq!(
 1386            editor.display_text(cx),
 1387            "
 1388                class Foo:
 1389                    # Hello!
 1390
 1391                    def a():
 1392                        print(1)
 1393
 1394                    def b():
 1395                        print(2)
 1396
 1397
 1398                class Bar:
 1399                    # World!
 1400
 1401                    def a():
 1402                        print(1)
 1403
 1404                    def b():
 1405                        print(2)
 1406
 1407
 1408            "
 1409            .unindent(),
 1410        );
 1411
 1412        assert_eq!(
 1413            editor.display_text(cx),
 1414            editor.buffer.read(cx).read(cx).text()
 1415        );
 1416        let (_, positions) = marked_text_ranges(
 1417            &"
 1418                       class Foo:
 1419                           # Hello!
 1420
 1421                           def a():
 1422                              print(1)
 1423
 1424                           def b():
 1425                               p«riˇ»nt(2)
 1426
 1427
 1428                       class Bar:
 1429                           # World!
 1430
 1431                           def a():
 1432                               «ˇprint(1)
 1433
 1434                           def b():
 1435                               print(2)»
 1436
 1437
 1438                   "
 1439            .unindent(),
 1440            true,
 1441        );
 1442
 1443        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 1444            s.select_ranges(
 1445                positions
 1446                    .iter()
 1447                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end)),
 1448            )
 1449        });
 1450
 1451        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1452        assert_eq!(
 1453            editor.display_text(cx),
 1454            "
 1455                class Foo:
 1456                    # Hello!
 1457
 1458                    def a():⋯
 1459
 1460                    def b():
 1461                        print(2)
 1462
 1463
 1464                class Bar:
 1465                    # World!
 1466
 1467                    def a():
 1468                        print(1)
 1469
 1470                    def b():
 1471                        print(2)
 1472
 1473
 1474            "
 1475            .unindent(),
 1476        );
 1477    });
 1478}
 1479
 1480#[gpui::test]
 1481fn test_move_cursor(cx: &mut TestAppContext) {
 1482    init_test(cx, |_| {});
 1483
 1484    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
 1485    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
 1486
 1487    buffer.update(cx, |buffer, cx| {
 1488        buffer.edit(
 1489            vec![
 1490                (Point::new(1, 0)..Point::new(1, 0), "\t"),
 1491                (Point::new(1, 1)..Point::new(1, 1), "\t"),
 1492            ],
 1493            None,
 1494            cx,
 1495        );
 1496    });
 1497    _ = editor.update(cx, |editor, window, cx| {
 1498        assert_eq!(
 1499            display_ranges(editor, cx),
 1500            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1501        );
 1502
 1503        editor.move_down(&MoveDown, window, cx);
 1504        assert_eq!(
 1505            display_ranges(editor, cx),
 1506            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1507        );
 1508
 1509        editor.move_right(&MoveRight, window, cx);
 1510        assert_eq!(
 1511            display_ranges(editor, cx),
 1512            &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
 1513        );
 1514
 1515        editor.move_left(&MoveLeft, window, cx);
 1516        assert_eq!(
 1517            display_ranges(editor, cx),
 1518            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1519        );
 1520
 1521        editor.move_up(&MoveUp, window, cx);
 1522        assert_eq!(
 1523            display_ranges(editor, cx),
 1524            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1525        );
 1526
 1527        editor.move_to_end(&MoveToEnd, window, cx);
 1528        assert_eq!(
 1529            display_ranges(editor, cx),
 1530            &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
 1531        );
 1532
 1533        editor.move_to_beginning(&MoveToBeginning, window, cx);
 1534        assert_eq!(
 1535            display_ranges(editor, cx),
 1536            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1537        );
 1538
 1539        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1540            s.select_display_ranges([
 1541                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
 1542            ]);
 1543        });
 1544        editor.select_to_beginning(&SelectToBeginning, window, cx);
 1545        assert_eq!(
 1546            display_ranges(editor, cx),
 1547            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
 1548        );
 1549
 1550        editor.select_to_end(&SelectToEnd, window, cx);
 1551        assert_eq!(
 1552            display_ranges(editor, cx),
 1553            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
 1554        );
 1555    });
 1556}
 1557
 1558#[gpui::test]
 1559fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 1560    init_test(cx, |_| {});
 1561
 1562    let editor = cx.add_window(|window, cx| {
 1563        let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
 1564        build_editor(buffer, window, cx)
 1565    });
 1566
 1567    assert_eq!('🟥'.len_utf8(), 4);
 1568    assert_eq!('α'.len_utf8(), 2);
 1569
 1570    _ = editor.update(cx, |editor, window, cx| {
 1571        editor.fold_creases(
 1572            vec![
 1573                Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
 1574                Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
 1575                Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
 1576            ],
 1577            true,
 1578            window,
 1579            cx,
 1580        );
 1581        assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
 1582
 1583        editor.move_right(&MoveRight, window, cx);
 1584        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥".len())]);
 1585        editor.move_right(&MoveRight, window, cx);
 1586        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥🟧".len())]);
 1587        editor.move_right(&MoveRight, window, cx);
 1588        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥🟧⋯".len())]);
 1589
 1590        editor.move_down(&MoveDown, window, cx);
 1591        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯e".len())]);
 1592        editor.move_left(&MoveLeft, window, cx);
 1593        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯".len())]);
 1594        editor.move_left(&MoveLeft, window, cx);
 1595        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab".len())]);
 1596        editor.move_left(&MoveLeft, window, cx);
 1597        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "a".len())]);
 1598
 1599        editor.move_down(&MoveDown, window, cx);
 1600        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "α".len())]);
 1601        editor.move_right(&MoveRight, window, cx);
 1602        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ".len())]);
 1603        editor.move_right(&MoveRight, window, cx);
 1604        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ⋯".len())]);
 1605        editor.move_right(&MoveRight, window, cx);
 1606        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ⋯ε".len())]);
 1607
 1608        editor.move_up(&MoveUp, window, cx);
 1609        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯e".len())]);
 1610        editor.move_down(&MoveDown, window, cx);
 1611        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ⋯ε".len())]);
 1612        editor.move_up(&MoveUp, window, cx);
 1613        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯e".len())]);
 1614
 1615        editor.move_up(&MoveUp, window, cx);
 1616        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥🟧".len())]);
 1617        editor.move_left(&MoveLeft, window, cx);
 1618        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥".len())]);
 1619        editor.move_left(&MoveLeft, window, cx);
 1620        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "".len())]);
 1621    });
 1622}
 1623
 1624#[gpui::test]
 1625fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
 1626    init_test(cx, |_| {});
 1627
 1628    let editor = cx.add_window(|window, cx| {
 1629        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
 1630        build_editor(buffer, window, cx)
 1631    });
 1632    _ = editor.update(cx, |editor, window, cx| {
 1633        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1634            s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
 1635        });
 1636
 1637        // moving above start of document should move selection to start of document,
 1638        // but the next move down should still be at the original goal_x
 1639        editor.move_up(&MoveUp, window, cx);
 1640        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "".len())]);
 1641
 1642        editor.move_down(&MoveDown, window, cx);
 1643        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "abcd".len())]);
 1644
 1645        editor.move_down(&MoveDown, window, cx);
 1646        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβγ".len())]);
 1647
 1648        editor.move_down(&MoveDown, window, cx);
 1649        assert_eq!(display_ranges(editor, cx), &[empty_range(3, "abcd".len())]);
 1650
 1651        editor.move_down(&MoveDown, window, cx);
 1652        assert_eq!(display_ranges(editor, cx), &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]);
 1653
 1654        // moving past end of document should not change goal_x
 1655        editor.move_down(&MoveDown, window, cx);
 1656        assert_eq!(display_ranges(editor, cx), &[empty_range(5, "".len())]);
 1657
 1658        editor.move_down(&MoveDown, window, cx);
 1659        assert_eq!(display_ranges(editor, cx), &[empty_range(5, "".len())]);
 1660
 1661        editor.move_up(&MoveUp, window, cx);
 1662        assert_eq!(display_ranges(editor, cx), &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]);
 1663
 1664        editor.move_up(&MoveUp, window, cx);
 1665        assert_eq!(display_ranges(editor, cx), &[empty_range(3, "abcd".len())]);
 1666
 1667        editor.move_up(&MoveUp, window, cx);
 1668        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβγ".len())]);
 1669    });
 1670}
 1671
 1672#[gpui::test]
 1673fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 1674    init_test(cx, |_| {});
 1675    let move_to_beg = MoveToBeginningOfLine {
 1676        stop_at_soft_wraps: true,
 1677        stop_at_indent: true,
 1678    };
 1679
 1680    let delete_to_beg = DeleteToBeginningOfLine {
 1681        stop_at_indent: false,
 1682    };
 1683
 1684    let move_to_end = MoveToEndOfLine {
 1685        stop_at_soft_wraps: true,
 1686    };
 1687
 1688    let editor = cx.add_window(|window, cx| {
 1689        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1690        build_editor(buffer, window, cx)
 1691    });
 1692    _ = editor.update(cx, |editor, window, cx| {
 1693        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1694            s.select_display_ranges([
 1695                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1696                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1697            ]);
 1698        });
 1699    });
 1700
 1701    _ = editor.update(cx, |editor, window, cx| {
 1702        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1703        assert_eq!(
 1704            display_ranges(editor, cx),
 1705            &[
 1706                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1707                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1708            ]
 1709        );
 1710    });
 1711
 1712    _ = editor.update(cx, |editor, window, cx| {
 1713        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1714        assert_eq!(
 1715            display_ranges(editor, cx),
 1716            &[
 1717                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1718                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1719            ]
 1720        );
 1721    });
 1722
 1723    _ = editor.update(cx, |editor, window, cx| {
 1724        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1725        assert_eq!(
 1726            display_ranges(editor, cx),
 1727            &[
 1728                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1729                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1730            ]
 1731        );
 1732    });
 1733
 1734    _ = editor.update(cx, |editor, window, cx| {
 1735        editor.move_to_end_of_line(&move_to_end, window, cx);
 1736        assert_eq!(
 1737            display_ranges(editor, cx),
 1738            &[
 1739                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1740                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1741            ]
 1742        );
 1743    });
 1744
 1745    // Moving to the end of line again is a no-op.
 1746    _ = editor.update(cx, |editor, window, cx| {
 1747        editor.move_to_end_of_line(&move_to_end, window, cx);
 1748        assert_eq!(
 1749            display_ranges(editor, cx),
 1750            &[
 1751                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1752                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1753            ]
 1754        );
 1755    });
 1756
 1757    _ = editor.update(cx, |editor, window, cx| {
 1758        editor.move_left(&MoveLeft, window, cx);
 1759        editor.select_to_beginning_of_line(
 1760            &SelectToBeginningOfLine {
 1761                stop_at_soft_wraps: true,
 1762                stop_at_indent: true,
 1763            },
 1764            window,
 1765            cx,
 1766        );
 1767        assert_eq!(
 1768            display_ranges(editor, cx),
 1769            &[
 1770                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1771                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1772            ]
 1773        );
 1774    });
 1775
 1776    _ = editor.update(cx, |editor, window, cx| {
 1777        editor.select_to_beginning_of_line(
 1778            &SelectToBeginningOfLine {
 1779                stop_at_soft_wraps: true,
 1780                stop_at_indent: true,
 1781            },
 1782            window,
 1783            cx,
 1784        );
 1785        assert_eq!(
 1786            display_ranges(editor, cx),
 1787            &[
 1788                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1789                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1790            ]
 1791        );
 1792    });
 1793
 1794    _ = editor.update(cx, |editor, window, cx| {
 1795        editor.select_to_beginning_of_line(
 1796            &SelectToBeginningOfLine {
 1797                stop_at_soft_wraps: true,
 1798                stop_at_indent: true,
 1799            },
 1800            window,
 1801            cx,
 1802        );
 1803        assert_eq!(
 1804            display_ranges(editor, cx),
 1805            &[
 1806                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1807                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1808            ]
 1809        );
 1810    });
 1811
 1812    _ = editor.update(cx, |editor, window, cx| {
 1813        editor.select_to_end_of_line(
 1814            &SelectToEndOfLine {
 1815                stop_at_soft_wraps: true,
 1816            },
 1817            window,
 1818            cx,
 1819        );
 1820        assert_eq!(
 1821            display_ranges(editor, cx),
 1822            &[
 1823                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
 1824                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
 1825            ]
 1826        );
 1827    });
 1828
 1829    _ = editor.update(cx, |editor, window, cx| {
 1830        editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
 1831        assert_eq!(editor.display_text(cx), "ab\n  de");
 1832        assert_eq!(
 1833            display_ranges(editor, cx),
 1834            &[
 1835                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 1836                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1837            ]
 1838        );
 1839    });
 1840
 1841    _ = editor.update(cx, |editor, window, cx| {
 1842        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1843        assert_eq!(editor.display_text(cx), "\n");
 1844        assert_eq!(
 1845            display_ranges(editor, cx),
 1846            &[
 1847                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1848                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1849            ]
 1850        );
 1851    });
 1852}
 1853
 1854#[gpui::test]
 1855fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
 1856    init_test(cx, |_| {});
 1857    let move_to_beg = MoveToBeginningOfLine {
 1858        stop_at_soft_wraps: false,
 1859        stop_at_indent: false,
 1860    };
 1861
 1862    let move_to_end = MoveToEndOfLine {
 1863        stop_at_soft_wraps: false,
 1864    };
 1865
 1866    let editor = cx.add_window(|window, cx| {
 1867        let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
 1868        build_editor(buffer, window, cx)
 1869    });
 1870
 1871    _ = editor.update(cx, |editor, window, cx| {
 1872        editor.set_wrap_width(Some(140.0.into()), cx);
 1873
 1874        // We expect the following lines after wrapping
 1875        // ```
 1876        // thequickbrownfox
 1877        // jumpedoverthelazydo
 1878        // gs
 1879        // ```
 1880        // The final `gs` was soft-wrapped onto a new line.
 1881        assert_eq!(
 1882            "thequickbrownfox\njumpedoverthelaz\nydogs",
 1883            editor.display_text(cx),
 1884        );
 1885
 1886        // First, let's assert behavior on the first line, that was not soft-wrapped.
 1887        // Start the cursor at the `k` on the first line
 1888        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1889            s.select_display_ranges([
 1890                DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
 1891            ]);
 1892        });
 1893
 1894        // Moving to the beginning of the line should put us at the beginning of the line.
 1895        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1896        assert_eq!(
 1897            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
 1898            display_ranges(editor, cx)
 1899        );
 1900
 1901        // Moving to the end of the line should put us at the end of the line.
 1902        editor.move_to_end_of_line(&move_to_end, window, cx);
 1903        assert_eq!(
 1904            vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
 1905            display_ranges(editor, cx)
 1906        );
 1907
 1908        // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
 1909        // Start the cursor at the last line (`y` that was wrapped to a new line)
 1910        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1911            s.select_display_ranges([
 1912                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
 1913            ]);
 1914        });
 1915
 1916        // Moving to the beginning of the line should put us at the start of the second line of
 1917        // display text, i.e., the `j`.
 1918        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1919        assert_eq!(
 1920            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1921            display_ranges(editor, cx)
 1922        );
 1923
 1924        // Moving to the beginning of the line again should be a no-op.
 1925        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1926        assert_eq!(
 1927            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1928            display_ranges(editor, cx)
 1929        );
 1930
 1931        // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
 1932        // next display line.
 1933        editor.move_to_end_of_line(&move_to_end, window, cx);
 1934        assert_eq!(
 1935            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1936            display_ranges(editor, cx)
 1937        );
 1938
 1939        // Moving to the end of the line again should be a no-op.
 1940        editor.move_to_end_of_line(&move_to_end, window, cx);
 1941        assert_eq!(
 1942            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1943            display_ranges(editor, cx)
 1944        );
 1945    });
 1946}
 1947
 1948#[gpui::test]
 1949fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
 1950    init_test(cx, |_| {});
 1951
 1952    let move_to_beg = MoveToBeginningOfLine {
 1953        stop_at_soft_wraps: true,
 1954        stop_at_indent: true,
 1955    };
 1956
 1957    let select_to_beg = SelectToBeginningOfLine {
 1958        stop_at_soft_wraps: true,
 1959        stop_at_indent: true,
 1960    };
 1961
 1962    let delete_to_beg = DeleteToBeginningOfLine {
 1963        stop_at_indent: true,
 1964    };
 1965
 1966    let move_to_end = MoveToEndOfLine {
 1967        stop_at_soft_wraps: false,
 1968    };
 1969
 1970    let editor = cx.add_window(|window, cx| {
 1971        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1972        build_editor(buffer, window, cx)
 1973    });
 1974
 1975    _ = editor.update(cx, |editor, window, cx| {
 1976        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1977            s.select_display_ranges([
 1978                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1979                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1980            ]);
 1981        });
 1982
 1983        // Moving to the beginning of the line should put the first cursor at the beginning of the line,
 1984        // and the second cursor at the first non-whitespace character in the line.
 1985        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1986        assert_eq!(
 1987            display_ranges(editor, cx),
 1988            &[
 1989                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1990                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1991            ]
 1992        );
 1993
 1994        // Moving to the beginning of the line again should be a no-op for the first cursor,
 1995        // and should move the second cursor to the beginning of the line.
 1996        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1997        assert_eq!(
 1998            display_ranges(editor, cx),
 1999            &[
 2000                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 2001                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 2002            ]
 2003        );
 2004
 2005        // Moving to the beginning of the line again should still be a no-op for the first cursor,
 2006        // and should move the second cursor back to the first non-whitespace character in the line.
 2007        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2008        assert_eq!(
 2009            display_ranges(editor, cx),
 2010            &[
 2011                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 2012                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2013            ]
 2014        );
 2015
 2016        // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
 2017        // and to the first non-whitespace character in the line for the second cursor.
 2018        editor.move_to_end_of_line(&move_to_end, window, cx);
 2019        editor.move_left(&MoveLeft, window, cx);
 2020        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 2021        assert_eq!(
 2022            display_ranges(editor, cx),
 2023            &[
 2024                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 2025                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 2026            ]
 2027        );
 2028
 2029        // Selecting to the beginning of the line again should be a no-op for the first cursor,
 2030        // and should select to the beginning of the line for the second cursor.
 2031        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 2032        assert_eq!(
 2033            display_ranges(editor, cx),
 2034            &[
 2035                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 2036                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 2037            ]
 2038        );
 2039
 2040        // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
 2041        // and should delete to the first non-whitespace character in the line for the second cursor.
 2042        editor.move_to_end_of_line(&move_to_end, window, cx);
 2043        editor.move_left(&MoveLeft, window, cx);
 2044        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 2045        assert_eq!(editor.text(cx), "c\n  f");
 2046    });
 2047}
 2048
 2049#[gpui::test]
 2050fn test_beginning_of_line_with_cursor_between_line_start_and_indent(cx: &mut TestAppContext) {
 2051    init_test(cx, |_| {});
 2052
 2053    let move_to_beg = MoveToBeginningOfLine {
 2054        stop_at_soft_wraps: true,
 2055        stop_at_indent: true,
 2056    };
 2057
 2058    let editor = cx.add_window(|window, cx| {
 2059        let buffer = MultiBuffer::build_simple("    hello\nworld", cx);
 2060        build_editor(buffer, window, cx)
 2061    });
 2062
 2063    _ = editor.update(cx, |editor, window, cx| {
 2064        // test cursor between line_start and indent_start
 2065        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2066            s.select_display_ranges([
 2067                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3)
 2068            ]);
 2069        });
 2070
 2071        // cursor should move to line_start
 2072        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2073        assert_eq!(
 2074            display_ranges(editor, cx),
 2075            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 2076        );
 2077
 2078        // cursor should move to indent_start
 2079        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2080        assert_eq!(
 2081            display_ranges(editor, cx),
 2082            &[DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 4)]
 2083        );
 2084
 2085        // cursor should move to back to line_start
 2086        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2087        assert_eq!(
 2088            display_ranges(editor, cx),
 2089            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 2090        );
 2091    });
 2092}
 2093
 2094#[gpui::test]
 2095fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 2096    init_test(cx, |_| {});
 2097
 2098    let editor = cx.add_window(|window, cx| {
 2099        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
 2100        build_editor(buffer, window, cx)
 2101    });
 2102    _ = editor.update(cx, |editor, window, cx| {
 2103        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2104            s.select_display_ranges([
 2105                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
 2106                DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
 2107            ])
 2108        });
 2109        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2110        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 2111
 2112        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2113        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\nˇ  {baz.qux()}", editor, cx);
 2114
 2115        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2116        assert_selection_ranges("use ˇstd::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 2117
 2118        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2119        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 2120
 2121        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2122        assert_selection_ranges("ˇuse std::str::{foo, ˇbar}\n\n  {baz.qux()}", editor, cx);
 2123
 2124        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2125        assert_selection_ranges("useˇ std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 2126
 2127        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2128        assert_selection_ranges("use stdˇ::str::{foo, bar}ˇ\n\n  {baz.qux()}", editor, cx);
 2129
 2130        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2131        assert_selection_ranges("use std::ˇstr::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 2132
 2133        editor.move_right(&MoveRight, window, cx);
 2134        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 2135        assert_selection_ranges(
 2136            "use std::«ˇs»tr::{foo, bar}\n«ˇ\n»  {baz.qux()}",
 2137            editor,
 2138            cx,
 2139        );
 2140
 2141        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 2142        assert_selection_ranges(
 2143            "use std«ˇ::s»tr::{foo, bar«ˇ}\n\n»  {baz.qux()}",
 2144            editor,
 2145            cx,
 2146        );
 2147
 2148        editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
 2149        assert_selection_ranges(
 2150            "use std::«ˇs»tr::{foo, bar}«ˇ\n\n»  {baz.qux()}",
 2151            editor,
 2152            cx,
 2153        );
 2154    });
 2155}
 2156
 2157#[gpui::test]
 2158fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 2159    init_test(cx, |_| {});
 2160
 2161    let editor = cx.add_window(|window, cx| {
 2162        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 2163        build_editor(buffer, window, cx)
 2164    });
 2165
 2166    _ = editor.update(cx, |editor, window, cx| {
 2167        editor.set_wrap_width(Some(140.0.into()), cx);
 2168        assert_eq!(
 2169            editor.display_text(cx),
 2170            "use one::{\n    two::three::\n    four::five\n};"
 2171        );
 2172
 2173        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2174            s.select_display_ranges([
 2175                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
 2176            ]);
 2177        });
 2178
 2179        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2180        assert_eq!(
 2181            display_ranges(editor, cx),
 2182            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
 2183        );
 2184
 2185        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2186        assert_eq!(
 2187            display_ranges(editor, cx),
 2188            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2189        );
 2190
 2191        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2192        assert_eq!(
 2193            display_ranges(editor, cx),
 2194            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2195        );
 2196
 2197        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2198        assert_eq!(
 2199            display_ranges(editor, cx),
 2200            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
 2201        );
 2202
 2203        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2204        assert_eq!(
 2205            display_ranges(editor, cx),
 2206            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2207        );
 2208
 2209        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2210        assert_eq!(
 2211            display_ranges(editor, cx),
 2212            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2213        );
 2214    });
 2215}
 2216
 2217#[gpui::test]
 2218async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
 2219    init_test(cx, |_| {});
 2220    let mut cx = EditorTestContext::new(cx).await;
 2221
 2222    let line_height = cx.update_editor(|editor, window, cx| {
 2223        editor
 2224            .style(cx)
 2225            .text
 2226            .line_height_in_pixels(window.rem_size())
 2227    });
 2228    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2229
 2230    cx.set_state(
 2231        &r#"ˇone
 2232        two
 2233
 2234        three
 2235        fourˇ
 2236        five
 2237
 2238        six"#
 2239            .unindent(),
 2240    );
 2241
 2242    cx.update_editor(|editor, window, cx| {
 2243        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2244    });
 2245    cx.assert_editor_state(
 2246        &r#"one
 2247        two
 2248        ˇ
 2249        three
 2250        four
 2251        five
 2252        ˇ
 2253        six"#
 2254            .unindent(),
 2255    );
 2256
 2257    cx.update_editor(|editor, window, cx| {
 2258        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2259    });
 2260    cx.assert_editor_state(
 2261        &r#"one
 2262        two
 2263
 2264        three
 2265        four
 2266        five
 2267        ˇ
 2268        sixˇ"#
 2269            .unindent(),
 2270    );
 2271
 2272    cx.update_editor(|editor, window, cx| {
 2273        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2274    });
 2275    cx.assert_editor_state(
 2276        &r#"one
 2277        two
 2278
 2279        three
 2280        four
 2281        five
 2282
 2283        sixˇ"#
 2284            .unindent(),
 2285    );
 2286
 2287    cx.update_editor(|editor, window, cx| {
 2288        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2289    });
 2290    cx.assert_editor_state(
 2291        &r#"one
 2292        two
 2293
 2294        three
 2295        four
 2296        five
 2297        ˇ
 2298        six"#
 2299            .unindent(),
 2300    );
 2301
 2302    cx.update_editor(|editor, window, cx| {
 2303        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2304    });
 2305    cx.assert_editor_state(
 2306        &r#"one
 2307        two
 2308        ˇ
 2309        three
 2310        four
 2311        five
 2312
 2313        six"#
 2314            .unindent(),
 2315    );
 2316
 2317    cx.update_editor(|editor, window, cx| {
 2318        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2319    });
 2320    cx.assert_editor_state(
 2321        &r#"ˇone
 2322        two
 2323
 2324        three
 2325        four
 2326        five
 2327
 2328        six"#
 2329            .unindent(),
 2330    );
 2331}
 2332
 2333#[gpui::test]
 2334async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2335    init_test(cx, |_| {});
 2336    let mut cx = EditorTestContext::new(cx).await;
 2337    let line_height = cx.update_editor(|editor, window, cx| {
 2338        editor
 2339            .style(cx)
 2340            .text
 2341            .line_height_in_pixels(window.rem_size())
 2342    });
 2343    let window = cx.window;
 2344    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2345
 2346    cx.set_state(
 2347        r#"ˇone
 2348        two
 2349        three
 2350        four
 2351        five
 2352        six
 2353        seven
 2354        eight
 2355        nine
 2356        ten
 2357        "#,
 2358    );
 2359
 2360    cx.update_editor(|editor, window, cx| {
 2361        assert_eq!(
 2362            editor.snapshot(window, cx).scroll_position(),
 2363            gpui::Point::new(0., 0.)
 2364        );
 2365        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2366        assert_eq!(
 2367            editor.snapshot(window, cx).scroll_position(),
 2368            gpui::Point::new(0., 3.)
 2369        );
 2370        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2371        assert_eq!(
 2372            editor.snapshot(window, cx).scroll_position(),
 2373            gpui::Point::new(0., 6.)
 2374        );
 2375        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2376        assert_eq!(
 2377            editor.snapshot(window, cx).scroll_position(),
 2378            gpui::Point::new(0., 3.)
 2379        );
 2380
 2381        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2382        assert_eq!(
 2383            editor.snapshot(window, cx).scroll_position(),
 2384            gpui::Point::new(0., 1.)
 2385        );
 2386        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2387        assert_eq!(
 2388            editor.snapshot(window, cx).scroll_position(),
 2389            gpui::Point::new(0., 3.)
 2390        );
 2391    });
 2392}
 2393
 2394#[gpui::test]
 2395async fn test_autoscroll(cx: &mut TestAppContext) {
 2396    init_test(cx, |_| {});
 2397    let mut cx = EditorTestContext::new(cx).await;
 2398
 2399    let line_height = cx.update_editor(|editor, window, cx| {
 2400        editor.set_vertical_scroll_margin(2, cx);
 2401        editor
 2402            .style(cx)
 2403            .text
 2404            .line_height_in_pixels(window.rem_size())
 2405    });
 2406    let window = cx.window;
 2407    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2408
 2409    cx.set_state(
 2410        r#"ˇone
 2411            two
 2412            three
 2413            four
 2414            five
 2415            six
 2416            seven
 2417            eight
 2418            nine
 2419            ten
 2420        "#,
 2421    );
 2422    cx.update_editor(|editor, window, cx| {
 2423        assert_eq!(
 2424            editor.snapshot(window, cx).scroll_position(),
 2425            gpui::Point::new(0., 0.0)
 2426        );
 2427    });
 2428
 2429    // Add a cursor below the visible area. Since both cursors cannot fit
 2430    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2431    // allows the vertical scroll margin below that cursor.
 2432    cx.update_editor(|editor, window, cx| {
 2433        editor.change_selections(Default::default(), window, cx, |selections| {
 2434            selections.select_ranges([
 2435                Point::new(0, 0)..Point::new(0, 0),
 2436                Point::new(6, 0)..Point::new(6, 0),
 2437            ]);
 2438        })
 2439    });
 2440    cx.update_editor(|editor, window, cx| {
 2441        assert_eq!(
 2442            editor.snapshot(window, cx).scroll_position(),
 2443            gpui::Point::new(0., 3.0)
 2444        );
 2445    });
 2446
 2447    // Move down. The editor cursor scrolls down to track the newest cursor.
 2448    cx.update_editor(|editor, window, cx| {
 2449        editor.move_down(&Default::default(), window, cx);
 2450    });
 2451    cx.update_editor(|editor, window, cx| {
 2452        assert_eq!(
 2453            editor.snapshot(window, cx).scroll_position(),
 2454            gpui::Point::new(0., 4.0)
 2455        );
 2456    });
 2457
 2458    // Add a cursor above the visible area. Since both cursors fit on screen,
 2459    // the editor scrolls to show both.
 2460    cx.update_editor(|editor, window, cx| {
 2461        editor.change_selections(Default::default(), window, cx, |selections| {
 2462            selections.select_ranges([
 2463                Point::new(1, 0)..Point::new(1, 0),
 2464                Point::new(6, 0)..Point::new(6, 0),
 2465            ]);
 2466        })
 2467    });
 2468    cx.update_editor(|editor, window, cx| {
 2469        assert_eq!(
 2470            editor.snapshot(window, cx).scroll_position(),
 2471            gpui::Point::new(0., 1.0)
 2472        );
 2473    });
 2474}
 2475
 2476#[gpui::test]
 2477async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2478    init_test(cx, |_| {});
 2479    let mut cx = EditorTestContext::new(cx).await;
 2480
 2481    let line_height = cx.update_editor(|editor, window, cx| {
 2482        editor
 2483            .style(cx)
 2484            .text
 2485            .line_height_in_pixels(window.rem_size())
 2486    });
 2487    let window = cx.window;
 2488    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2489    cx.set_state(
 2490        &r#"
 2491        ˇone
 2492        two
 2493        threeˇ
 2494        four
 2495        five
 2496        six
 2497        seven
 2498        eight
 2499        nine
 2500        ten
 2501        "#
 2502        .unindent(),
 2503    );
 2504
 2505    cx.update_editor(|editor, window, cx| {
 2506        editor.move_page_down(&MovePageDown::default(), window, cx)
 2507    });
 2508    cx.assert_editor_state(
 2509        &r#"
 2510        one
 2511        two
 2512        three
 2513        ˇfour
 2514        five
 2515        sixˇ
 2516        seven
 2517        eight
 2518        nine
 2519        ten
 2520        "#
 2521        .unindent(),
 2522    );
 2523
 2524    cx.update_editor(|editor, window, cx| {
 2525        editor.move_page_down(&MovePageDown::default(), window, cx)
 2526    });
 2527    cx.assert_editor_state(
 2528        &r#"
 2529        one
 2530        two
 2531        three
 2532        four
 2533        five
 2534        six
 2535        ˇseven
 2536        eight
 2537        nineˇ
 2538        ten
 2539        "#
 2540        .unindent(),
 2541    );
 2542
 2543    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2544    cx.assert_editor_state(
 2545        &r#"
 2546        one
 2547        two
 2548        three
 2549        ˇfour
 2550        five
 2551        sixˇ
 2552        seven
 2553        eight
 2554        nine
 2555        ten
 2556        "#
 2557        .unindent(),
 2558    );
 2559
 2560    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2561    cx.assert_editor_state(
 2562        &r#"
 2563        ˇone
 2564        two
 2565        threeˇ
 2566        four
 2567        five
 2568        six
 2569        seven
 2570        eight
 2571        nine
 2572        ten
 2573        "#
 2574        .unindent(),
 2575    );
 2576
 2577    // Test select collapsing
 2578    cx.update_editor(|editor, window, cx| {
 2579        editor.move_page_down(&MovePageDown::default(), window, cx);
 2580        editor.move_page_down(&MovePageDown::default(), window, cx);
 2581        editor.move_page_down(&MovePageDown::default(), window, cx);
 2582    });
 2583    cx.assert_editor_state(
 2584        &r#"
 2585        one
 2586        two
 2587        three
 2588        four
 2589        five
 2590        six
 2591        seven
 2592        eight
 2593        nine
 2594        ˇten
 2595        ˇ"#
 2596        .unindent(),
 2597    );
 2598}
 2599
 2600#[gpui::test]
 2601async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2602    init_test(cx, |_| {});
 2603    let mut cx = EditorTestContext::new(cx).await;
 2604    cx.set_state("one «two threeˇ» four");
 2605    cx.update_editor(|editor, window, cx| {
 2606        editor.delete_to_beginning_of_line(
 2607            &DeleteToBeginningOfLine {
 2608                stop_at_indent: false,
 2609            },
 2610            window,
 2611            cx,
 2612        );
 2613        assert_eq!(editor.text(cx), " four");
 2614    });
 2615}
 2616
 2617#[gpui::test]
 2618async fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2619    init_test(cx, |_| {});
 2620
 2621    let mut cx = EditorTestContext::new(cx).await;
 2622
 2623    // For an empty selection, the preceding word fragment is deleted.
 2624    // For non-empty selections, only selected characters are deleted.
 2625    cx.set_state("onˇe two t«hreˇ»e four");
 2626    cx.update_editor(|editor, window, cx| {
 2627        editor.delete_to_previous_word_start(
 2628            &DeleteToPreviousWordStart {
 2629                ignore_newlines: false,
 2630                ignore_brackets: false,
 2631            },
 2632            window,
 2633            cx,
 2634        );
 2635    });
 2636    cx.assert_editor_state("ˇe two tˇe four");
 2637
 2638    cx.set_state("e tˇwo te «fˇ»our");
 2639    cx.update_editor(|editor, window, cx| {
 2640        editor.delete_to_next_word_end(
 2641            &DeleteToNextWordEnd {
 2642                ignore_newlines: false,
 2643                ignore_brackets: false,
 2644            },
 2645            window,
 2646            cx,
 2647        );
 2648    });
 2649    cx.assert_editor_state("e tˇ te ˇour");
 2650}
 2651
 2652#[gpui::test]
 2653async fn test_delete_whitespaces(cx: &mut TestAppContext) {
 2654    init_test(cx, |_| {});
 2655
 2656    let mut cx = EditorTestContext::new(cx).await;
 2657
 2658    cx.set_state("here is some text    ˇwith a space");
 2659    cx.update_editor(|editor, window, cx| {
 2660        editor.delete_to_previous_word_start(
 2661            &DeleteToPreviousWordStart {
 2662                ignore_newlines: false,
 2663                ignore_brackets: true,
 2664            },
 2665            window,
 2666            cx,
 2667        );
 2668    });
 2669    // Continuous whitespace sequences are removed entirely, words behind them are not affected by the deletion action.
 2670    cx.assert_editor_state("here is some textˇwith a space");
 2671
 2672    cx.set_state("here is some text    ˇwith a space");
 2673    cx.update_editor(|editor, window, cx| {
 2674        editor.delete_to_previous_word_start(
 2675            &DeleteToPreviousWordStart {
 2676                ignore_newlines: false,
 2677                ignore_brackets: false,
 2678            },
 2679            window,
 2680            cx,
 2681        );
 2682    });
 2683    cx.assert_editor_state("here is some textˇwith a space");
 2684
 2685    cx.set_state("here is some textˇ    with a space");
 2686    cx.update_editor(|editor, window, cx| {
 2687        editor.delete_to_next_word_end(
 2688            &DeleteToNextWordEnd {
 2689                ignore_newlines: false,
 2690                ignore_brackets: true,
 2691            },
 2692            window,
 2693            cx,
 2694        );
 2695    });
 2696    // Same happens in the other direction.
 2697    cx.assert_editor_state("here is some textˇwith a space");
 2698
 2699    cx.set_state("here is some textˇ    with a space");
 2700    cx.update_editor(|editor, window, cx| {
 2701        editor.delete_to_next_word_end(
 2702            &DeleteToNextWordEnd {
 2703                ignore_newlines: false,
 2704                ignore_brackets: false,
 2705            },
 2706            window,
 2707            cx,
 2708        );
 2709    });
 2710    cx.assert_editor_state("here is some textˇwith a space");
 2711
 2712    cx.set_state("here is some textˇ    with a space");
 2713    cx.update_editor(|editor, window, cx| {
 2714        editor.delete_to_next_word_end(
 2715            &DeleteToNextWordEnd {
 2716                ignore_newlines: true,
 2717                ignore_brackets: false,
 2718            },
 2719            window,
 2720            cx,
 2721        );
 2722    });
 2723    cx.assert_editor_state("here is some textˇwith a space");
 2724    cx.update_editor(|editor, window, cx| {
 2725        editor.delete_to_previous_word_start(
 2726            &DeleteToPreviousWordStart {
 2727                ignore_newlines: true,
 2728                ignore_brackets: false,
 2729            },
 2730            window,
 2731            cx,
 2732        );
 2733    });
 2734    cx.assert_editor_state("here is some ˇwith a space");
 2735    cx.update_editor(|editor, window, cx| {
 2736        editor.delete_to_previous_word_start(
 2737            &DeleteToPreviousWordStart {
 2738                ignore_newlines: true,
 2739                ignore_brackets: false,
 2740            },
 2741            window,
 2742            cx,
 2743        );
 2744    });
 2745    // Single whitespaces are removed with the word behind them.
 2746    cx.assert_editor_state("here is ˇwith a space");
 2747    cx.update_editor(|editor, window, cx| {
 2748        editor.delete_to_previous_word_start(
 2749            &DeleteToPreviousWordStart {
 2750                ignore_newlines: true,
 2751                ignore_brackets: false,
 2752            },
 2753            window,
 2754            cx,
 2755        );
 2756    });
 2757    cx.assert_editor_state("here ˇwith a space");
 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    cx.assert_editor_state("ˇwith a space");
 2769    cx.update_editor(|editor, window, cx| {
 2770        editor.delete_to_previous_word_start(
 2771            &DeleteToPreviousWordStart {
 2772                ignore_newlines: true,
 2773                ignore_brackets: false,
 2774            },
 2775            window,
 2776            cx,
 2777        );
 2778    });
 2779    cx.assert_editor_state("ˇwith a space");
 2780    cx.update_editor(|editor, window, cx| {
 2781        editor.delete_to_next_word_end(
 2782            &DeleteToNextWordEnd {
 2783                ignore_newlines: true,
 2784                ignore_brackets: false,
 2785            },
 2786            window,
 2787            cx,
 2788        );
 2789    });
 2790    // Same happens in the other direction.
 2791    cx.assert_editor_state("ˇ a space");
 2792    cx.update_editor(|editor, window, cx| {
 2793        editor.delete_to_next_word_end(
 2794            &DeleteToNextWordEnd {
 2795                ignore_newlines: true,
 2796                ignore_brackets: false,
 2797            },
 2798            window,
 2799            cx,
 2800        );
 2801    });
 2802    cx.assert_editor_state("ˇ space");
 2803    cx.update_editor(|editor, window, cx| {
 2804        editor.delete_to_next_word_end(
 2805            &DeleteToNextWordEnd {
 2806                ignore_newlines: true,
 2807                ignore_brackets: false,
 2808            },
 2809            window,
 2810            cx,
 2811        );
 2812    });
 2813    cx.assert_editor_state("ˇ");
 2814    cx.update_editor(|editor, window, cx| {
 2815        editor.delete_to_next_word_end(
 2816            &DeleteToNextWordEnd {
 2817                ignore_newlines: true,
 2818                ignore_brackets: false,
 2819            },
 2820            window,
 2821            cx,
 2822        );
 2823    });
 2824    cx.assert_editor_state("ˇ");
 2825    cx.update_editor(|editor, window, cx| {
 2826        editor.delete_to_previous_word_start(
 2827            &DeleteToPreviousWordStart {
 2828                ignore_newlines: true,
 2829                ignore_brackets: false,
 2830            },
 2831            window,
 2832            cx,
 2833        );
 2834    });
 2835    cx.assert_editor_state("ˇ");
 2836}
 2837
 2838#[gpui::test]
 2839async fn test_delete_to_bracket(cx: &mut TestAppContext) {
 2840    init_test(cx, |_| {});
 2841
 2842    let language = Arc::new(
 2843        Language::new(
 2844            LanguageConfig {
 2845                brackets: BracketPairConfig {
 2846                    pairs: vec![
 2847                        BracketPair {
 2848                            start: "\"".to_string(),
 2849                            end: "\"".to_string(),
 2850                            close: true,
 2851                            surround: true,
 2852                            newline: false,
 2853                        },
 2854                        BracketPair {
 2855                            start: "(".to_string(),
 2856                            end: ")".to_string(),
 2857                            close: true,
 2858                            surround: true,
 2859                            newline: true,
 2860                        },
 2861                    ],
 2862                    ..BracketPairConfig::default()
 2863                },
 2864                ..LanguageConfig::default()
 2865            },
 2866            Some(tree_sitter_rust::LANGUAGE.into()),
 2867        )
 2868        .with_brackets_query(
 2869            r#"
 2870                ("(" @open ")" @close)
 2871                ("\"" @open "\"" @close)
 2872            "#,
 2873        )
 2874        .unwrap(),
 2875    );
 2876
 2877    let mut cx = EditorTestContext::new(cx).await;
 2878    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2879
 2880    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 2881    cx.update_editor(|editor, window, cx| {
 2882        editor.delete_to_previous_word_start(
 2883            &DeleteToPreviousWordStart {
 2884                ignore_newlines: true,
 2885                ignore_brackets: false,
 2886            },
 2887            window,
 2888            cx,
 2889        );
 2890    });
 2891    // Deletion stops before brackets if asked to not ignore them.
 2892    cx.assert_editor_state(r#"macro!("ˇCOMMENT");"#);
 2893    cx.update_editor(|editor, window, cx| {
 2894        editor.delete_to_previous_word_start(
 2895            &DeleteToPreviousWordStart {
 2896                ignore_newlines: true,
 2897                ignore_brackets: false,
 2898            },
 2899            window,
 2900            cx,
 2901        );
 2902    });
 2903    // Deletion has to remove a single bracket and then stop again.
 2904    cx.assert_editor_state(r#"macro!(ˇCOMMENT");"#);
 2905
 2906    cx.update_editor(|editor, window, cx| {
 2907        editor.delete_to_previous_word_start(
 2908            &DeleteToPreviousWordStart {
 2909                ignore_newlines: true,
 2910                ignore_brackets: false,
 2911            },
 2912            window,
 2913            cx,
 2914        );
 2915    });
 2916    cx.assert_editor_state(r#"macro!ˇCOMMENT");"#);
 2917
 2918    cx.update_editor(|editor, window, cx| {
 2919        editor.delete_to_previous_word_start(
 2920            &DeleteToPreviousWordStart {
 2921                ignore_newlines: true,
 2922                ignore_brackets: false,
 2923            },
 2924            window,
 2925            cx,
 2926        );
 2927    });
 2928    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2929
 2930    cx.update_editor(|editor, window, cx| {
 2931        editor.delete_to_previous_word_start(
 2932            &DeleteToPreviousWordStart {
 2933                ignore_newlines: true,
 2934                ignore_brackets: false,
 2935            },
 2936            window,
 2937            cx,
 2938        );
 2939    });
 2940    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2941
 2942    cx.update_editor(|editor, window, cx| {
 2943        editor.delete_to_next_word_end(
 2944            &DeleteToNextWordEnd {
 2945                ignore_newlines: true,
 2946                ignore_brackets: false,
 2947            },
 2948            window,
 2949            cx,
 2950        );
 2951    });
 2952    // Brackets on the right are not paired anymore, hence deletion does not stop at them
 2953    cx.assert_editor_state(r#"ˇ");"#);
 2954
 2955    cx.update_editor(|editor, window, cx| {
 2956        editor.delete_to_next_word_end(
 2957            &DeleteToNextWordEnd {
 2958                ignore_newlines: true,
 2959                ignore_brackets: false,
 2960            },
 2961            window,
 2962            cx,
 2963        );
 2964    });
 2965    cx.assert_editor_state(r#"ˇ"#);
 2966
 2967    cx.update_editor(|editor, window, cx| {
 2968        editor.delete_to_next_word_end(
 2969            &DeleteToNextWordEnd {
 2970                ignore_newlines: true,
 2971                ignore_brackets: false,
 2972            },
 2973            window,
 2974            cx,
 2975        );
 2976    });
 2977    cx.assert_editor_state(r#"ˇ"#);
 2978
 2979    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 2980    cx.update_editor(|editor, window, cx| {
 2981        editor.delete_to_previous_word_start(
 2982            &DeleteToPreviousWordStart {
 2983                ignore_newlines: true,
 2984                ignore_brackets: true,
 2985            },
 2986            window,
 2987            cx,
 2988        );
 2989    });
 2990    cx.assert_editor_state(r#"macroˇCOMMENT");"#);
 2991}
 2992
 2993#[gpui::test]
 2994fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 2995    init_test(cx, |_| {});
 2996
 2997    let editor = cx.add_window(|window, cx| {
 2998        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 2999        build_editor(buffer, window, cx)
 3000    });
 3001    let del_to_prev_word_start = DeleteToPreviousWordStart {
 3002        ignore_newlines: false,
 3003        ignore_brackets: false,
 3004    };
 3005    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 3006        ignore_newlines: true,
 3007        ignore_brackets: false,
 3008    };
 3009
 3010    _ = editor.update(cx, |editor, window, cx| {
 3011        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3012            s.select_display_ranges([
 3013                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 3014            ])
 3015        });
 3016        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3017        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 3018        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3019        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
 3020        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3021        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
 3022        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3023        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
 3024        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 3025        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 3026        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 3027        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3028    });
 3029}
 3030
 3031#[gpui::test]
 3032fn test_delete_to_previous_subword_start_or_newline(cx: &mut TestAppContext) {
 3033    init_test(cx, |_| {});
 3034
 3035    let editor = cx.add_window(|window, cx| {
 3036        let buffer = MultiBuffer::build_simple("fooBar\n\nbazQux", cx);
 3037        build_editor(buffer, window, cx)
 3038    });
 3039    let del_to_prev_sub_word_start = DeleteToPreviousSubwordStart {
 3040        ignore_newlines: false,
 3041        ignore_brackets: false,
 3042    };
 3043    let del_to_prev_sub_word_start_ignore_newlines = DeleteToPreviousSubwordStart {
 3044        ignore_newlines: true,
 3045        ignore_brackets: false,
 3046    };
 3047
 3048    _ = editor.update(cx, |editor, window, cx| {
 3049        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3050            s.select_display_ranges([
 3051                DisplayPoint::new(DisplayRow(2), 6)..DisplayPoint::new(DisplayRow(2), 6)
 3052            ])
 3053        });
 3054        editor.delete_to_previous_subword_start(&del_to_prev_sub_word_start, window, cx);
 3055        assert_eq!(editor.buffer.read(cx).read(cx).text(), "fooBar\n\nbaz");
 3056        editor.delete_to_previous_subword_start(&del_to_prev_sub_word_start, window, cx);
 3057        assert_eq!(editor.buffer.read(cx).read(cx).text(), "fooBar\n\n");
 3058        editor.delete_to_previous_subword_start(&del_to_prev_sub_word_start, window, cx);
 3059        assert_eq!(editor.buffer.read(cx).read(cx).text(), "fooBar\n");
 3060        editor.delete_to_previous_subword_start(&del_to_prev_sub_word_start, window, cx);
 3061        assert_eq!(editor.buffer.read(cx).read(cx).text(), "fooBar");
 3062        editor.delete_to_previous_subword_start(&del_to_prev_sub_word_start, window, cx);
 3063        assert_eq!(editor.buffer.read(cx).read(cx).text(), "foo");
 3064        editor.delete_to_previous_subword_start(
 3065            &del_to_prev_sub_word_start_ignore_newlines,
 3066            window,
 3067            cx,
 3068        );
 3069        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3070    });
 3071}
 3072
 3073#[gpui::test]
 3074fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 3075    init_test(cx, |_| {});
 3076
 3077    let editor = cx.add_window(|window, cx| {
 3078        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 3079        build_editor(buffer, window, cx)
 3080    });
 3081    let del_to_next_word_end = DeleteToNextWordEnd {
 3082        ignore_newlines: false,
 3083        ignore_brackets: false,
 3084    };
 3085    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 3086        ignore_newlines: true,
 3087        ignore_brackets: false,
 3088    };
 3089
 3090    _ = editor.update(cx, |editor, window, cx| {
 3091        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3092            s.select_display_ranges([
 3093                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 3094            ])
 3095        });
 3096        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3097        assert_eq!(
 3098            editor.buffer.read(cx).read(cx).text(),
 3099            "one\n   two\nthree\n   four"
 3100        );
 3101        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3102        assert_eq!(
 3103            editor.buffer.read(cx).read(cx).text(),
 3104            "\n   two\nthree\n   four"
 3105        );
 3106        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3107        assert_eq!(
 3108            editor.buffer.read(cx).read(cx).text(),
 3109            "two\nthree\n   four"
 3110        );
 3111        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3112        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 3113        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3114        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   four");
 3115        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3116        assert_eq!(editor.buffer.read(cx).read(cx).text(), "four");
 3117        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3118        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3119    });
 3120}
 3121
 3122#[gpui::test]
 3123fn test_delete_to_next_subword_end_or_newline(cx: &mut TestAppContext) {
 3124    init_test(cx, |_| {});
 3125
 3126    let editor = cx.add_window(|window, cx| {
 3127        let buffer = MultiBuffer::build_simple("\nfooBar\n   bazQux", cx);
 3128        build_editor(buffer, window, cx)
 3129    });
 3130    let del_to_next_subword_end = DeleteToNextSubwordEnd {
 3131        ignore_newlines: false,
 3132        ignore_brackets: false,
 3133    };
 3134    let del_to_next_subword_end_ignore_newlines = DeleteToNextSubwordEnd {
 3135        ignore_newlines: true,
 3136        ignore_brackets: false,
 3137    };
 3138
 3139    _ = editor.update(cx, |editor, window, cx| {
 3140        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3141            s.select_display_ranges([
 3142                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 3143            ])
 3144        });
 3145        // Delete "\n" (empty line)
 3146        editor.delete_to_next_subword_end(&del_to_next_subword_end, window, cx);
 3147        assert_eq!(editor.buffer.read(cx).read(cx).text(), "fooBar\n   bazQux");
 3148        // Delete "foo" (subword boundary)
 3149        editor.delete_to_next_subword_end(&del_to_next_subword_end, window, cx);
 3150        assert_eq!(editor.buffer.read(cx).read(cx).text(), "Bar\n   bazQux");
 3151        // Delete "Bar"
 3152        editor.delete_to_next_subword_end(&del_to_next_subword_end, window, cx);
 3153        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   bazQux");
 3154        // Delete "\n   " (newline + leading whitespace)
 3155        editor.delete_to_next_subword_end(&del_to_next_subword_end, window, cx);
 3156        assert_eq!(editor.buffer.read(cx).read(cx).text(), "bazQux");
 3157        // Delete "baz" (subword boundary)
 3158        editor.delete_to_next_subword_end(&del_to_next_subword_end, window, cx);
 3159        assert_eq!(editor.buffer.read(cx).read(cx).text(), "Qux");
 3160        // With ignore_newlines, delete "Qux"
 3161        editor.delete_to_next_subword_end(&del_to_next_subword_end_ignore_newlines, window, cx);
 3162        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3163    });
 3164}
 3165
 3166#[gpui::test]
 3167fn test_newline(cx: &mut TestAppContext) {
 3168    init_test(cx, |_| {});
 3169
 3170    let editor = cx.add_window(|window, cx| {
 3171        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 3172        build_editor(buffer, window, cx)
 3173    });
 3174
 3175    _ = editor.update(cx, |editor, window, cx| {
 3176        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3177            s.select_display_ranges([
 3178                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 3179                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 3180                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 3181            ])
 3182        });
 3183
 3184        editor.newline(&Newline, window, cx);
 3185        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 3186    });
 3187}
 3188
 3189#[gpui::test]
 3190async fn test_newline_yaml(cx: &mut TestAppContext) {
 3191    init_test(cx, |_| {});
 3192
 3193    let mut cx = EditorTestContext::new(cx).await;
 3194    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3195    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3196
 3197    // Object (between 2 fields)
 3198    cx.set_state(indoc! {"
 3199    test:ˇ
 3200    hello: bye"});
 3201    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3202    cx.assert_editor_state(indoc! {"
 3203    test:
 3204        ˇ
 3205    hello: bye"});
 3206
 3207    // Object (first and single line)
 3208    cx.set_state(indoc! {"
 3209    test:ˇ"});
 3210    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3211    cx.assert_editor_state(indoc! {"
 3212    test:
 3213        ˇ"});
 3214
 3215    // Array with objects (after first element)
 3216    cx.set_state(indoc! {"
 3217    test:
 3218        - foo: barˇ"});
 3219    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3220    cx.assert_editor_state(indoc! {"
 3221    test:
 3222        - foo: bar
 3223        ˇ"});
 3224
 3225    // Array with objects and comment
 3226    cx.set_state(indoc! {"
 3227    test:
 3228        - foo: bar
 3229        - bar: # testˇ"});
 3230    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3231    cx.assert_editor_state(indoc! {"
 3232    test:
 3233        - foo: bar
 3234        - bar: # test
 3235            ˇ"});
 3236
 3237    // Array with objects (after second element)
 3238    cx.set_state(indoc! {"
 3239    test:
 3240        - foo: bar
 3241        - bar: fooˇ"});
 3242    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3243    cx.assert_editor_state(indoc! {"
 3244    test:
 3245        - foo: bar
 3246        - bar: foo
 3247        ˇ"});
 3248
 3249    // Array with strings (after first element)
 3250    cx.set_state(indoc! {"
 3251    test:
 3252        - fooˇ"});
 3253    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3254    cx.assert_editor_state(indoc! {"
 3255    test:
 3256        - foo
 3257        ˇ"});
 3258}
 3259
 3260#[gpui::test]
 3261fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 3262    init_test(cx, |_| {});
 3263
 3264    let editor = cx.add_window(|window, cx| {
 3265        let buffer = MultiBuffer::build_simple(
 3266            "
 3267                a
 3268                b(
 3269                    X
 3270                )
 3271                c(
 3272                    X
 3273                )
 3274            "
 3275            .unindent()
 3276            .as_str(),
 3277            cx,
 3278        );
 3279        let mut editor = build_editor(buffer, window, cx);
 3280        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3281            s.select_ranges([
 3282                Point::new(2, 4)..Point::new(2, 5),
 3283                Point::new(5, 4)..Point::new(5, 5),
 3284            ])
 3285        });
 3286        editor
 3287    });
 3288
 3289    _ = editor.update(cx, |editor, window, cx| {
 3290        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3291        editor.buffer.update(cx, |buffer, cx| {
 3292            buffer.edit(
 3293                [
 3294                    (Point::new(1, 2)..Point::new(3, 0), ""),
 3295                    (Point::new(4, 2)..Point::new(6, 0), ""),
 3296                ],
 3297                None,
 3298                cx,
 3299            );
 3300            assert_eq!(
 3301                buffer.read(cx).text(),
 3302                "
 3303                    a
 3304                    b()
 3305                    c()
 3306                "
 3307                .unindent()
 3308            );
 3309        });
 3310        assert_eq!(
 3311            editor.selections.ranges(&editor.display_snapshot(cx)),
 3312            &[
 3313                Point::new(1, 2)..Point::new(1, 2),
 3314                Point::new(2, 2)..Point::new(2, 2),
 3315            ],
 3316        );
 3317
 3318        editor.newline(&Newline, window, cx);
 3319        assert_eq!(
 3320            editor.text(cx),
 3321            "
 3322                a
 3323                b(
 3324                )
 3325                c(
 3326                )
 3327            "
 3328            .unindent()
 3329        );
 3330
 3331        // The selections are moved after the inserted newlines
 3332        assert_eq!(
 3333            editor.selections.ranges(&editor.display_snapshot(cx)),
 3334            &[
 3335                Point::new(2, 0)..Point::new(2, 0),
 3336                Point::new(4, 0)..Point::new(4, 0),
 3337            ],
 3338        );
 3339    });
 3340}
 3341
 3342#[gpui::test]
 3343async fn test_newline_above(cx: &mut TestAppContext) {
 3344    init_test(cx, |settings| {
 3345        settings.defaults.tab_size = NonZeroU32::new(4)
 3346    });
 3347
 3348    let language = Arc::new(
 3349        Language::new(
 3350            LanguageConfig::default(),
 3351            Some(tree_sitter_rust::LANGUAGE.into()),
 3352        )
 3353        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3354        .unwrap(),
 3355    );
 3356
 3357    let mut cx = EditorTestContext::new(cx).await;
 3358    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3359    cx.set_state(indoc! {"
 3360        const a: ˇA = (
 3361 3362                «const_functionˇ»(ˇ),
 3363                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3364 3365        ˇ);ˇ
 3366    "});
 3367
 3368    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 3369    cx.assert_editor_state(indoc! {"
 3370        ˇ
 3371        const a: A = (
 3372            ˇ
 3373            (
 3374                ˇ
 3375                ˇ
 3376                const_function(),
 3377                ˇ
 3378                ˇ
 3379                ˇ
 3380                ˇ
 3381                something_else,
 3382                ˇ
 3383            )
 3384            ˇ
 3385            ˇ
 3386        );
 3387    "});
 3388}
 3389
 3390#[gpui::test]
 3391async fn test_newline_below(cx: &mut TestAppContext) {
 3392    init_test(cx, |settings| {
 3393        settings.defaults.tab_size = NonZeroU32::new(4)
 3394    });
 3395
 3396    let language = Arc::new(
 3397        Language::new(
 3398            LanguageConfig::default(),
 3399            Some(tree_sitter_rust::LANGUAGE.into()),
 3400        )
 3401        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3402        .unwrap(),
 3403    );
 3404
 3405    let mut cx = EditorTestContext::new(cx).await;
 3406    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3407    cx.set_state(indoc! {"
 3408        const a: ˇA = (
 3409 3410                «const_functionˇ»(ˇ),
 3411                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3412 3413        ˇ);ˇ
 3414    "});
 3415
 3416    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 3417    cx.assert_editor_state(indoc! {"
 3418        const a: A = (
 3419            ˇ
 3420            (
 3421                ˇ
 3422                const_function(),
 3423                ˇ
 3424                ˇ
 3425                something_else,
 3426                ˇ
 3427                ˇ
 3428                ˇ
 3429                ˇ
 3430            )
 3431            ˇ
 3432        );
 3433        ˇ
 3434        ˇ
 3435    "});
 3436}
 3437
 3438#[gpui::test]
 3439async fn test_newline_comments(cx: &mut TestAppContext) {
 3440    init_test(cx, |settings| {
 3441        settings.defaults.tab_size = NonZeroU32::new(4)
 3442    });
 3443
 3444    let language = Arc::new(Language::new(
 3445        LanguageConfig {
 3446            line_comments: vec!["// ".into()],
 3447            ..LanguageConfig::default()
 3448        },
 3449        None,
 3450    ));
 3451    {
 3452        let mut cx = EditorTestContext::new(cx).await;
 3453        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3454        cx.set_state(indoc! {"
 3455        // Fooˇ
 3456    "});
 3457
 3458        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3459        cx.assert_editor_state(indoc! {"
 3460        // Foo
 3461        // ˇ
 3462    "});
 3463        // Ensure that we add comment prefix when existing line contains space
 3464        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3465        cx.assert_editor_state(
 3466            indoc! {"
 3467        // Foo
 3468        //s
 3469        // ˇ
 3470    "}
 3471            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3472            .as_str(),
 3473        );
 3474        // Ensure that we add comment prefix when existing line does not contain space
 3475        cx.set_state(indoc! {"
 3476        // Foo
 3477        //ˇ
 3478    "});
 3479        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3480        cx.assert_editor_state(indoc! {"
 3481        // Foo
 3482        //
 3483        // ˇ
 3484    "});
 3485        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 3486        cx.set_state(indoc! {"
 3487        ˇ// Foo
 3488    "});
 3489        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3490        cx.assert_editor_state(indoc! {"
 3491
 3492        ˇ// Foo
 3493    "});
 3494    }
 3495    // Ensure that comment continuations can be disabled.
 3496    update_test_language_settings(cx, |settings| {
 3497        settings.defaults.extend_comment_on_newline = Some(false);
 3498    });
 3499    let mut cx = EditorTestContext::new(cx).await;
 3500    cx.set_state(indoc! {"
 3501        // Fooˇ
 3502    "});
 3503    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3504    cx.assert_editor_state(indoc! {"
 3505        // Foo
 3506        ˇ
 3507    "});
 3508}
 3509
 3510#[gpui::test]
 3511async fn test_newline_comments_with_multiple_delimiters(cx: &mut TestAppContext) {
 3512    init_test(cx, |settings| {
 3513        settings.defaults.tab_size = NonZeroU32::new(4)
 3514    });
 3515
 3516    let language = Arc::new(Language::new(
 3517        LanguageConfig {
 3518            line_comments: vec!["// ".into(), "/// ".into()],
 3519            ..LanguageConfig::default()
 3520        },
 3521        None,
 3522    ));
 3523    {
 3524        let mut cx = EditorTestContext::new(cx).await;
 3525        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3526        cx.set_state(indoc! {"
 3527        //ˇ
 3528    "});
 3529        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3530        cx.assert_editor_state(indoc! {"
 3531        //
 3532        // ˇ
 3533    "});
 3534
 3535        cx.set_state(indoc! {"
 3536        ///ˇ
 3537    "});
 3538        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3539        cx.assert_editor_state(indoc! {"
 3540        ///
 3541        /// ˇ
 3542    "});
 3543    }
 3544}
 3545
 3546#[gpui::test]
 3547async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 3548    init_test(cx, |settings| {
 3549        settings.defaults.tab_size = NonZeroU32::new(4)
 3550    });
 3551
 3552    let language = Arc::new(
 3553        Language::new(
 3554            LanguageConfig {
 3555                documentation_comment: Some(language::BlockCommentConfig {
 3556                    start: "/**".into(),
 3557                    end: "*/".into(),
 3558                    prefix: "* ".into(),
 3559                    tab_size: 1,
 3560                }),
 3561
 3562                ..LanguageConfig::default()
 3563            },
 3564            Some(tree_sitter_rust::LANGUAGE.into()),
 3565        )
 3566        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 3567        .unwrap(),
 3568    );
 3569
 3570    {
 3571        let mut cx = EditorTestContext::new(cx).await;
 3572        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3573        cx.set_state(indoc! {"
 3574        /**ˇ
 3575    "});
 3576
 3577        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3578        cx.assert_editor_state(indoc! {"
 3579        /**
 3580         * ˇ
 3581    "});
 3582        // Ensure that if cursor is before the comment start,
 3583        // we do not actually insert a comment prefix.
 3584        cx.set_state(indoc! {"
 3585        ˇ/**
 3586    "});
 3587        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3588        cx.assert_editor_state(indoc! {"
 3589
 3590        ˇ/**
 3591    "});
 3592        // Ensure that if cursor is between it doesn't add comment prefix.
 3593        cx.set_state(indoc! {"
 3594        /*ˇ*
 3595    "});
 3596        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3597        cx.assert_editor_state(indoc! {"
 3598        /*
 3599        ˇ*
 3600    "});
 3601        // Ensure that if suffix exists on same line after cursor it adds new line.
 3602        cx.set_state(indoc! {"
 3603        /**ˇ*/
 3604    "});
 3605        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3606        cx.assert_editor_state(indoc! {"
 3607        /**
 3608         * ˇ
 3609         */
 3610    "});
 3611        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3612        cx.set_state(indoc! {"
 3613        /**ˇ */
 3614    "});
 3615        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3616        cx.assert_editor_state(indoc! {"
 3617        /**
 3618         * ˇ
 3619         */
 3620    "});
 3621        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3622        cx.set_state(indoc! {"
 3623        /** ˇ*/
 3624    "});
 3625        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3626        cx.assert_editor_state(
 3627            indoc! {"
 3628        /**s
 3629         * ˇ
 3630         */
 3631    "}
 3632            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3633            .as_str(),
 3634        );
 3635        // Ensure that delimiter space is preserved when newline on already
 3636        // spaced delimiter.
 3637        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3638        cx.assert_editor_state(
 3639            indoc! {"
 3640        /**s
 3641         *s
 3642         * ˇ
 3643         */
 3644    "}
 3645            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3646            .as_str(),
 3647        );
 3648        // Ensure that delimiter space is preserved when space is not
 3649        // on existing delimiter.
 3650        cx.set_state(indoc! {"
 3651        /**
 3652 3653         */
 3654    "});
 3655        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3656        cx.assert_editor_state(indoc! {"
 3657        /**
 3658         *
 3659         * ˇ
 3660         */
 3661    "});
 3662        // Ensure that if suffix exists on same line after cursor it
 3663        // doesn't add extra new line if prefix is not on same line.
 3664        cx.set_state(indoc! {"
 3665        /**
 3666        ˇ*/
 3667    "});
 3668        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3669        cx.assert_editor_state(indoc! {"
 3670        /**
 3671
 3672        ˇ*/
 3673    "});
 3674        // Ensure that it detects suffix after existing prefix.
 3675        cx.set_state(indoc! {"
 3676        /**ˇ/
 3677    "});
 3678        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3679        cx.assert_editor_state(indoc! {"
 3680        /**
 3681        ˇ/
 3682    "});
 3683        // Ensure that if suffix exists on same line before
 3684        // cursor it does not add comment prefix.
 3685        cx.set_state(indoc! {"
 3686        /** */ˇ
 3687    "});
 3688        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3689        cx.assert_editor_state(indoc! {"
 3690        /** */
 3691        ˇ
 3692    "});
 3693        // Ensure that if suffix exists on same line before
 3694        // cursor it does not add comment prefix.
 3695        cx.set_state(indoc! {"
 3696        /**
 3697         *
 3698         */ˇ
 3699    "});
 3700        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3701        cx.assert_editor_state(indoc! {"
 3702        /**
 3703         *
 3704         */
 3705         ˇ
 3706    "});
 3707
 3708        // Ensure that inline comment followed by code
 3709        // doesn't add comment prefix on newline
 3710        cx.set_state(indoc! {"
 3711        /** */ textˇ
 3712    "});
 3713        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3714        cx.assert_editor_state(indoc! {"
 3715        /** */ text
 3716        ˇ
 3717    "});
 3718
 3719        // Ensure that text after comment end tag
 3720        // doesn't add comment prefix on newline
 3721        cx.set_state(indoc! {"
 3722        /**
 3723         *
 3724         */ˇtext
 3725    "});
 3726        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3727        cx.assert_editor_state(indoc! {"
 3728        /**
 3729         *
 3730         */
 3731         ˇtext
 3732    "});
 3733
 3734        // Ensure if not comment block it doesn't
 3735        // add comment prefix on newline
 3736        cx.set_state(indoc! {"
 3737        * textˇ
 3738    "});
 3739        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3740        cx.assert_editor_state(indoc! {"
 3741        * text
 3742        ˇ
 3743    "});
 3744    }
 3745    // Ensure that comment continuations can be disabled.
 3746    update_test_language_settings(cx, |settings| {
 3747        settings.defaults.extend_comment_on_newline = Some(false);
 3748    });
 3749    let mut cx = EditorTestContext::new(cx).await;
 3750    cx.set_state(indoc! {"
 3751        /**ˇ
 3752    "});
 3753    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3754    cx.assert_editor_state(indoc! {"
 3755        /**
 3756        ˇ
 3757    "});
 3758}
 3759
 3760#[gpui::test]
 3761async fn test_newline_comments_with_block_comment(cx: &mut TestAppContext) {
 3762    init_test(cx, |settings| {
 3763        settings.defaults.tab_size = NonZeroU32::new(4)
 3764    });
 3765
 3766    let lua_language = Arc::new(Language::new(
 3767        LanguageConfig {
 3768            line_comments: vec!["--".into()],
 3769            block_comment: Some(language::BlockCommentConfig {
 3770                start: "--[[".into(),
 3771                prefix: "".into(),
 3772                end: "]]".into(),
 3773                tab_size: 0,
 3774            }),
 3775            ..LanguageConfig::default()
 3776        },
 3777        None,
 3778    ));
 3779
 3780    let mut cx = EditorTestContext::new(cx).await;
 3781    cx.update_buffer(|buffer, cx| buffer.set_language(Some(lua_language), cx));
 3782
 3783    // Line with line comment should extend
 3784    cx.set_state(indoc! {"
 3785        --ˇ
 3786    "});
 3787    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3788    cx.assert_editor_state(indoc! {"
 3789        --
 3790        --ˇ
 3791    "});
 3792
 3793    // Line with block comment that matches line comment should not extend
 3794    cx.set_state(indoc! {"
 3795        --[[ˇ
 3796    "});
 3797    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3798    cx.assert_editor_state(indoc! {"
 3799        --[[
 3800        ˇ
 3801    "});
 3802}
 3803
 3804#[gpui::test]
 3805fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 3806    init_test(cx, |_| {});
 3807
 3808    let editor = cx.add_window(|window, cx| {
 3809        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 3810        let mut editor = build_editor(buffer, window, cx);
 3811        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3812            s.select_ranges([
 3813                MultiBufferOffset(3)..MultiBufferOffset(4),
 3814                MultiBufferOffset(11)..MultiBufferOffset(12),
 3815                MultiBufferOffset(19)..MultiBufferOffset(20),
 3816            ])
 3817        });
 3818        editor
 3819    });
 3820
 3821    _ = editor.update(cx, |editor, window, cx| {
 3822        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3823        editor.buffer.update(cx, |buffer, cx| {
 3824            buffer.edit(
 3825                [
 3826                    (MultiBufferOffset(2)..MultiBufferOffset(5), ""),
 3827                    (MultiBufferOffset(10)..MultiBufferOffset(13), ""),
 3828                    (MultiBufferOffset(18)..MultiBufferOffset(21), ""),
 3829                ],
 3830                None,
 3831                cx,
 3832            );
 3833            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 3834        });
 3835        assert_eq!(
 3836            editor.selections.ranges(&editor.display_snapshot(cx)),
 3837            &[
 3838                MultiBufferOffset(2)..MultiBufferOffset(2),
 3839                MultiBufferOffset(7)..MultiBufferOffset(7),
 3840                MultiBufferOffset(12)..MultiBufferOffset(12)
 3841            ],
 3842        );
 3843
 3844        editor.insert("Z", window, cx);
 3845        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 3846
 3847        // The selections are moved after the inserted characters
 3848        assert_eq!(
 3849            editor.selections.ranges(&editor.display_snapshot(cx)),
 3850            &[
 3851                MultiBufferOffset(3)..MultiBufferOffset(3),
 3852                MultiBufferOffset(9)..MultiBufferOffset(9),
 3853                MultiBufferOffset(15)..MultiBufferOffset(15)
 3854            ],
 3855        );
 3856    });
 3857}
 3858
 3859#[gpui::test]
 3860async fn test_tab(cx: &mut TestAppContext) {
 3861    init_test(cx, |settings| {
 3862        settings.defaults.tab_size = NonZeroU32::new(3)
 3863    });
 3864
 3865    let mut cx = EditorTestContext::new(cx).await;
 3866    cx.set_state(indoc! {"
 3867        ˇabˇc
 3868        ˇ🏀ˇ🏀ˇefg
 3869 3870    "});
 3871    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3872    cx.assert_editor_state(indoc! {"
 3873           ˇab ˇc
 3874           ˇ🏀  ˇ🏀  ˇefg
 3875        d  ˇ
 3876    "});
 3877
 3878    cx.set_state(indoc! {"
 3879        a
 3880        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3881    "});
 3882    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3883    cx.assert_editor_state(indoc! {"
 3884        a
 3885           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3886    "});
 3887}
 3888
 3889#[gpui::test]
 3890async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3891    init_test(cx, |_| {});
 3892
 3893    let mut cx = EditorTestContext::new(cx).await;
 3894    let language = Arc::new(
 3895        Language::new(
 3896            LanguageConfig::default(),
 3897            Some(tree_sitter_rust::LANGUAGE.into()),
 3898        )
 3899        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3900        .unwrap(),
 3901    );
 3902    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3903
 3904    // test when all cursors are not at suggested indent
 3905    // then simply move to their suggested indent location
 3906    cx.set_state(indoc! {"
 3907        const a: B = (
 3908            c(
 3909        ˇ
 3910        ˇ    )
 3911        );
 3912    "});
 3913    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3914    cx.assert_editor_state(indoc! {"
 3915        const a: B = (
 3916            c(
 3917                ˇ
 3918            ˇ)
 3919        );
 3920    "});
 3921
 3922    // test cursor already at suggested indent not moving when
 3923    // other cursors are yet to reach their suggested indents
 3924    cx.set_state(indoc! {"
 3925        ˇ
 3926        const a: B = (
 3927            c(
 3928                d(
 3929        ˇ
 3930                )
 3931        ˇ
 3932        ˇ    )
 3933        );
 3934    "});
 3935    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3936    cx.assert_editor_state(indoc! {"
 3937        ˇ
 3938        const a: B = (
 3939            c(
 3940                d(
 3941                    ˇ
 3942                )
 3943                ˇ
 3944            ˇ)
 3945        );
 3946    "});
 3947    // test when all cursors are at suggested indent then tab is inserted
 3948    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3949    cx.assert_editor_state(indoc! {"
 3950            ˇ
 3951        const a: B = (
 3952            c(
 3953                d(
 3954                        ˇ
 3955                )
 3956                    ˇ
 3957                ˇ)
 3958        );
 3959    "});
 3960
 3961    // test when current indent is less than suggested indent,
 3962    // we adjust line to match suggested indent and move cursor to it
 3963    //
 3964    // when no other cursor is at word boundary, all of them should move
 3965    cx.set_state(indoc! {"
 3966        const a: B = (
 3967            c(
 3968                d(
 3969        ˇ
 3970        ˇ   )
 3971        ˇ   )
 3972        );
 3973    "});
 3974    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3975    cx.assert_editor_state(indoc! {"
 3976        const a: B = (
 3977            c(
 3978                d(
 3979                    ˇ
 3980                ˇ)
 3981            ˇ)
 3982        );
 3983    "});
 3984
 3985    // test when current indent is less than suggested indent,
 3986    // we adjust line to match suggested indent and move cursor to it
 3987    //
 3988    // when some other cursor is at word boundary, it should not move
 3989    cx.set_state(indoc! {"
 3990        const a: B = (
 3991            c(
 3992                d(
 3993        ˇ
 3994        ˇ   )
 3995           ˇ)
 3996        );
 3997    "});
 3998    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3999    cx.assert_editor_state(indoc! {"
 4000        const a: B = (
 4001            c(
 4002                d(
 4003                    ˇ
 4004                ˇ)
 4005            ˇ)
 4006        );
 4007    "});
 4008
 4009    // test when current indent is more than suggested indent,
 4010    // we just move cursor to current indent instead of suggested indent
 4011    //
 4012    // when no other cursor is at word boundary, all of them should move
 4013    cx.set_state(indoc! {"
 4014        const a: B = (
 4015            c(
 4016                d(
 4017        ˇ
 4018        ˇ                )
 4019        ˇ   )
 4020        );
 4021    "});
 4022    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4023    cx.assert_editor_state(indoc! {"
 4024        const a: B = (
 4025            c(
 4026                d(
 4027                    ˇ
 4028                        ˇ)
 4029            ˇ)
 4030        );
 4031    "});
 4032    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4033    cx.assert_editor_state(indoc! {"
 4034        const a: B = (
 4035            c(
 4036                d(
 4037                        ˇ
 4038                            ˇ)
 4039                ˇ)
 4040        );
 4041    "});
 4042
 4043    // test when current indent is more than suggested indent,
 4044    // we just move cursor to current indent instead of suggested indent
 4045    //
 4046    // when some other cursor is at word boundary, it doesn't move
 4047    cx.set_state(indoc! {"
 4048        const a: B = (
 4049            c(
 4050                d(
 4051        ˇ
 4052        ˇ                )
 4053            ˇ)
 4054        );
 4055    "});
 4056    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4057    cx.assert_editor_state(indoc! {"
 4058        const a: B = (
 4059            c(
 4060                d(
 4061                    ˇ
 4062                        ˇ)
 4063            ˇ)
 4064        );
 4065    "});
 4066
 4067    // handle auto-indent when there are multiple cursors on the same line
 4068    cx.set_state(indoc! {"
 4069        const a: B = (
 4070            c(
 4071        ˇ    ˇ
 4072        ˇ    )
 4073        );
 4074    "});
 4075    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4076    cx.assert_editor_state(indoc! {"
 4077        const a: B = (
 4078            c(
 4079                ˇ
 4080            ˇ)
 4081        );
 4082    "});
 4083}
 4084
 4085#[gpui::test]
 4086async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 4087    init_test(cx, |settings| {
 4088        settings.defaults.tab_size = NonZeroU32::new(3)
 4089    });
 4090
 4091    let mut cx = EditorTestContext::new(cx).await;
 4092    cx.set_state(indoc! {"
 4093         ˇ
 4094        \t ˇ
 4095        \t  ˇ
 4096        \t   ˇ
 4097         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 4098    "});
 4099
 4100    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4101    cx.assert_editor_state(indoc! {"
 4102           ˇ
 4103        \t   ˇ
 4104        \t   ˇ
 4105        \t      ˇ
 4106         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 4107    "});
 4108}
 4109
 4110#[gpui::test]
 4111async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 4112    init_test(cx, |settings| {
 4113        settings.defaults.tab_size = NonZeroU32::new(4)
 4114    });
 4115
 4116    let language = Arc::new(
 4117        Language::new(
 4118            LanguageConfig::default(),
 4119            Some(tree_sitter_rust::LANGUAGE.into()),
 4120        )
 4121        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 4122        .unwrap(),
 4123    );
 4124
 4125    let mut cx = EditorTestContext::new(cx).await;
 4126    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 4127    cx.set_state(indoc! {"
 4128        fn a() {
 4129            if b {
 4130        \t ˇc
 4131            }
 4132        }
 4133    "});
 4134
 4135    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4136    cx.assert_editor_state(indoc! {"
 4137        fn a() {
 4138            if b {
 4139                ˇc
 4140            }
 4141        }
 4142    "});
 4143}
 4144
 4145#[gpui::test]
 4146async fn test_indent_outdent(cx: &mut TestAppContext) {
 4147    init_test(cx, |settings| {
 4148        settings.defaults.tab_size = NonZeroU32::new(4);
 4149    });
 4150
 4151    let mut cx = EditorTestContext::new(cx).await;
 4152
 4153    cx.set_state(indoc! {"
 4154          «oneˇ» «twoˇ»
 4155        three
 4156         four
 4157    "});
 4158    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4159    cx.assert_editor_state(indoc! {"
 4160            «oneˇ» «twoˇ»
 4161        three
 4162         four
 4163    "});
 4164
 4165    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4166    cx.assert_editor_state(indoc! {"
 4167        «oneˇ» «twoˇ»
 4168        three
 4169         four
 4170    "});
 4171
 4172    // select across line ending
 4173    cx.set_state(indoc! {"
 4174        one two
 4175        t«hree
 4176        ˇ» four
 4177    "});
 4178    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4179    cx.assert_editor_state(indoc! {"
 4180        one two
 4181            t«hree
 4182        ˇ» four
 4183    "});
 4184
 4185    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4186    cx.assert_editor_state(indoc! {"
 4187        one two
 4188        t«hree
 4189        ˇ» four
 4190    "});
 4191
 4192    // Ensure that indenting/outdenting works when the cursor is at column 0.
 4193    cx.set_state(indoc! {"
 4194        one two
 4195        ˇthree
 4196            four
 4197    "});
 4198    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4199    cx.assert_editor_state(indoc! {"
 4200        one two
 4201            ˇthree
 4202            four
 4203    "});
 4204
 4205    cx.set_state(indoc! {"
 4206        one two
 4207        ˇ    three
 4208            four
 4209    "});
 4210    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4211    cx.assert_editor_state(indoc! {"
 4212        one two
 4213        ˇthree
 4214            four
 4215    "});
 4216}
 4217
 4218#[gpui::test]
 4219async fn test_indent_yaml_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 4220    // This is a regression test for issue #33761
 4221    init_test(cx, |_| {});
 4222
 4223    let mut cx = EditorTestContext::new(cx).await;
 4224    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 4225    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 4226
 4227    cx.set_state(
 4228        r#"ˇ#     ingress:
 4229ˇ#         api:
 4230ˇ#             enabled: false
 4231ˇ#             pathType: Prefix
 4232ˇ#           console:
 4233ˇ#               enabled: false
 4234ˇ#               pathType: Prefix
 4235"#,
 4236    );
 4237
 4238    // Press tab to indent all lines
 4239    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4240
 4241    cx.assert_editor_state(
 4242        r#"    ˇ#     ingress:
 4243    ˇ#         api:
 4244    ˇ#             enabled: false
 4245    ˇ#             pathType: Prefix
 4246    ˇ#           console:
 4247    ˇ#               enabled: false
 4248    ˇ#               pathType: Prefix
 4249"#,
 4250    );
 4251}
 4252
 4253#[gpui::test]
 4254async fn test_indent_yaml_non_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 4255    // This is a test to make sure our fix for issue #33761 didn't break anything
 4256    init_test(cx, |_| {});
 4257
 4258    let mut cx = EditorTestContext::new(cx).await;
 4259    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 4260    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 4261
 4262    cx.set_state(
 4263        r#"ˇingress:
 4264ˇ  api:
 4265ˇ    enabled: false
 4266ˇ    pathType: Prefix
 4267"#,
 4268    );
 4269
 4270    // Press tab to indent all lines
 4271    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4272
 4273    cx.assert_editor_state(
 4274        r#"ˇingress:
 4275    ˇapi:
 4276        ˇenabled: false
 4277        ˇpathType: Prefix
 4278"#,
 4279    );
 4280}
 4281
 4282#[gpui::test]
 4283async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 4284    init_test(cx, |settings| {
 4285        settings.defaults.hard_tabs = Some(true);
 4286    });
 4287
 4288    let mut cx = EditorTestContext::new(cx).await;
 4289
 4290    // select two ranges on one line
 4291    cx.set_state(indoc! {"
 4292        «oneˇ» «twoˇ»
 4293        three
 4294        four
 4295    "});
 4296    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4297    cx.assert_editor_state(indoc! {"
 4298        \t«oneˇ» «twoˇ»
 4299        three
 4300        four
 4301    "});
 4302    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4303    cx.assert_editor_state(indoc! {"
 4304        \t\t«oneˇ» «twoˇ»
 4305        three
 4306        four
 4307    "});
 4308    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4309    cx.assert_editor_state(indoc! {"
 4310        \t«oneˇ» «twoˇ»
 4311        three
 4312        four
 4313    "});
 4314    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4315    cx.assert_editor_state(indoc! {"
 4316        «oneˇ» «twoˇ»
 4317        three
 4318        four
 4319    "});
 4320
 4321    // select across a line ending
 4322    cx.set_state(indoc! {"
 4323        one two
 4324        t«hree
 4325        ˇ»four
 4326    "});
 4327    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4328    cx.assert_editor_state(indoc! {"
 4329        one two
 4330        \tt«hree
 4331        ˇ»four
 4332    "});
 4333    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4334    cx.assert_editor_state(indoc! {"
 4335        one two
 4336        \t\tt«hree
 4337        ˇ»four
 4338    "});
 4339    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4340    cx.assert_editor_state(indoc! {"
 4341        one two
 4342        \tt«hree
 4343        ˇ»four
 4344    "});
 4345    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4346    cx.assert_editor_state(indoc! {"
 4347        one two
 4348        t«hree
 4349        ˇ»four
 4350    "});
 4351
 4352    // Ensure that indenting/outdenting works when the cursor is at column 0.
 4353    cx.set_state(indoc! {"
 4354        one two
 4355        ˇthree
 4356        four
 4357    "});
 4358    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4359    cx.assert_editor_state(indoc! {"
 4360        one two
 4361        ˇthree
 4362        four
 4363    "});
 4364    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4365    cx.assert_editor_state(indoc! {"
 4366        one two
 4367        \tˇthree
 4368        four
 4369    "});
 4370    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4371    cx.assert_editor_state(indoc! {"
 4372        one two
 4373        ˇthree
 4374        four
 4375    "});
 4376}
 4377
 4378#[gpui::test]
 4379fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 4380    init_test(cx, |settings| {
 4381        settings.languages.0.extend([
 4382            (
 4383                "TOML".into(),
 4384                LanguageSettingsContent {
 4385                    tab_size: NonZeroU32::new(2),
 4386                    ..Default::default()
 4387                },
 4388            ),
 4389            (
 4390                "Rust".into(),
 4391                LanguageSettingsContent {
 4392                    tab_size: NonZeroU32::new(4),
 4393                    ..Default::default()
 4394                },
 4395            ),
 4396        ]);
 4397    });
 4398
 4399    let toml_language = Arc::new(Language::new(
 4400        LanguageConfig {
 4401            name: "TOML".into(),
 4402            ..Default::default()
 4403        },
 4404        None,
 4405    ));
 4406    let rust_language = Arc::new(Language::new(
 4407        LanguageConfig {
 4408            name: "Rust".into(),
 4409            ..Default::default()
 4410        },
 4411        None,
 4412    ));
 4413
 4414    let toml_buffer =
 4415        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 4416    let rust_buffer =
 4417        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 4418    let multibuffer = cx.new(|cx| {
 4419        let mut multibuffer = MultiBuffer::new(ReadWrite);
 4420        multibuffer.push_excerpts(
 4421            toml_buffer.clone(),
 4422            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 4423            cx,
 4424        );
 4425        multibuffer.push_excerpts(
 4426            rust_buffer.clone(),
 4427            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 4428            cx,
 4429        );
 4430        multibuffer
 4431    });
 4432
 4433    cx.add_window(|window, cx| {
 4434        let mut editor = build_editor(multibuffer, window, cx);
 4435
 4436        assert_eq!(
 4437            editor.text(cx),
 4438            indoc! {"
 4439                a = 1
 4440                b = 2
 4441
 4442                const c: usize = 3;
 4443            "}
 4444        );
 4445
 4446        select_ranges(
 4447            &mut editor,
 4448            indoc! {"
 4449                «aˇ» = 1
 4450                b = 2
 4451
 4452                «const c:ˇ» usize = 3;
 4453            "},
 4454            window,
 4455            cx,
 4456        );
 4457
 4458        editor.tab(&Tab, window, cx);
 4459        assert_text_with_selections(
 4460            &mut editor,
 4461            indoc! {"
 4462                  «aˇ» = 1
 4463                b = 2
 4464
 4465                    «const c:ˇ» usize = 3;
 4466            "},
 4467            cx,
 4468        );
 4469        editor.backtab(&Backtab, window, cx);
 4470        assert_text_with_selections(
 4471            &mut editor,
 4472            indoc! {"
 4473                «aˇ» = 1
 4474                b = 2
 4475
 4476                «const c:ˇ» usize = 3;
 4477            "},
 4478            cx,
 4479        );
 4480
 4481        editor
 4482    });
 4483}
 4484
 4485#[gpui::test]
 4486async fn test_backspace(cx: &mut TestAppContext) {
 4487    init_test(cx, |_| {});
 4488
 4489    let mut cx = EditorTestContext::new(cx).await;
 4490
 4491    // Basic backspace
 4492    cx.set_state(indoc! {"
 4493        onˇe two three
 4494        fou«rˇ» five six
 4495        seven «ˇeight nine
 4496        »ten
 4497    "});
 4498    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4499    cx.assert_editor_state(indoc! {"
 4500        oˇe two three
 4501        fouˇ five six
 4502        seven ˇten
 4503    "});
 4504
 4505    // Test backspace inside and around indents
 4506    cx.set_state(indoc! {"
 4507        zero
 4508            ˇone
 4509                ˇtwo
 4510            ˇ ˇ ˇ  three
 4511        ˇ  ˇ  four
 4512    "});
 4513    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4514    cx.assert_editor_state(indoc! {"
 4515        zero
 4516        ˇone
 4517            ˇtwo
 4518        ˇ  threeˇ  four
 4519    "});
 4520}
 4521
 4522#[gpui::test]
 4523async fn test_delete(cx: &mut TestAppContext) {
 4524    init_test(cx, |_| {});
 4525
 4526    let mut cx = EditorTestContext::new(cx).await;
 4527    cx.set_state(indoc! {"
 4528        onˇe two three
 4529        fou«rˇ» five six
 4530        seven «ˇeight nine
 4531        »ten
 4532    "});
 4533    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 4534    cx.assert_editor_state(indoc! {"
 4535        onˇ two three
 4536        fouˇ five six
 4537        seven ˇten
 4538    "});
 4539}
 4540
 4541#[gpui::test]
 4542fn test_delete_line(cx: &mut TestAppContext) {
 4543    init_test(cx, |_| {});
 4544
 4545    let editor = cx.add_window(|window, cx| {
 4546        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4547        build_editor(buffer, window, cx)
 4548    });
 4549    _ = editor.update(cx, |editor, window, cx| {
 4550        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4551            s.select_display_ranges([
 4552                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4553                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4554                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4555            ])
 4556        });
 4557        editor.delete_line(&DeleteLine, window, cx);
 4558        assert_eq!(editor.display_text(cx), "ghi");
 4559        assert_eq!(
 4560            display_ranges(editor, cx),
 4561            vec![
 4562                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 4563                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4564            ]
 4565        );
 4566    });
 4567
 4568    let editor = cx.add_window(|window, cx| {
 4569        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4570        build_editor(buffer, window, cx)
 4571    });
 4572    _ = editor.update(cx, |editor, window, cx| {
 4573        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4574            s.select_display_ranges([
 4575                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 4576            ])
 4577        });
 4578        editor.delete_line(&DeleteLine, window, cx);
 4579        assert_eq!(editor.display_text(cx), "ghi\n");
 4580        assert_eq!(
 4581            display_ranges(editor, cx),
 4582            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 4583        );
 4584    });
 4585
 4586    let editor = cx.add_window(|window, cx| {
 4587        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n\njkl\nmno", cx);
 4588        build_editor(buffer, window, cx)
 4589    });
 4590    _ = editor.update(cx, |editor, window, cx| {
 4591        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4592            s.select_display_ranges([
 4593                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(2), 1)
 4594            ])
 4595        });
 4596        editor.delete_line(&DeleteLine, window, cx);
 4597        assert_eq!(editor.display_text(cx), "\njkl\nmno");
 4598        assert_eq!(
 4599            display_ranges(editor, cx),
 4600            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 4601        );
 4602    });
 4603}
 4604
 4605#[gpui::test]
 4606fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 4607    init_test(cx, |_| {});
 4608
 4609    cx.add_window(|window, cx| {
 4610        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4611        let mut editor = build_editor(buffer.clone(), window, cx);
 4612        let buffer = buffer.read(cx).as_singleton().unwrap();
 4613
 4614        assert_eq!(
 4615            editor
 4616                .selections
 4617                .ranges::<Point>(&editor.display_snapshot(cx)),
 4618            &[Point::new(0, 0)..Point::new(0, 0)]
 4619        );
 4620
 4621        // When on single line, replace newline at end by space
 4622        editor.join_lines(&JoinLines, window, cx);
 4623        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4624        assert_eq!(
 4625            editor
 4626                .selections
 4627                .ranges::<Point>(&editor.display_snapshot(cx)),
 4628            &[Point::new(0, 3)..Point::new(0, 3)]
 4629        );
 4630
 4631        // When multiple lines are selected, remove newlines that are spanned by the selection
 4632        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4633            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 4634        });
 4635        editor.join_lines(&JoinLines, window, cx);
 4636        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 4637        assert_eq!(
 4638            editor
 4639                .selections
 4640                .ranges::<Point>(&editor.display_snapshot(cx)),
 4641            &[Point::new(0, 11)..Point::new(0, 11)]
 4642        );
 4643
 4644        // Undo should be transactional
 4645        editor.undo(&Undo, window, cx);
 4646        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4647        assert_eq!(
 4648            editor
 4649                .selections
 4650                .ranges::<Point>(&editor.display_snapshot(cx)),
 4651            &[Point::new(0, 5)..Point::new(2, 2)]
 4652        );
 4653
 4654        // When joining an empty line don't insert a space
 4655        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4656            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 4657        });
 4658        editor.join_lines(&JoinLines, window, cx);
 4659        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 4660        assert_eq!(
 4661            editor
 4662                .selections
 4663                .ranges::<Point>(&editor.display_snapshot(cx)),
 4664            [Point::new(2, 3)..Point::new(2, 3)]
 4665        );
 4666
 4667        // We can remove trailing newlines
 4668        editor.join_lines(&JoinLines, window, cx);
 4669        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4670        assert_eq!(
 4671            editor
 4672                .selections
 4673                .ranges::<Point>(&editor.display_snapshot(cx)),
 4674            [Point::new(2, 3)..Point::new(2, 3)]
 4675        );
 4676
 4677        // We don't blow up on the last line
 4678        editor.join_lines(&JoinLines, window, cx);
 4679        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4680        assert_eq!(
 4681            editor
 4682                .selections
 4683                .ranges::<Point>(&editor.display_snapshot(cx)),
 4684            [Point::new(2, 3)..Point::new(2, 3)]
 4685        );
 4686
 4687        // reset to test indentation
 4688        editor.buffer.update(cx, |buffer, cx| {
 4689            buffer.edit(
 4690                [
 4691                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 4692                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 4693                ],
 4694                None,
 4695                cx,
 4696            )
 4697        });
 4698
 4699        // We remove any leading spaces
 4700        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 4701        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4702            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 4703        });
 4704        editor.join_lines(&JoinLines, window, cx);
 4705        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 4706
 4707        // We don't insert a space for a line containing only spaces
 4708        editor.join_lines(&JoinLines, window, cx);
 4709        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 4710
 4711        // We ignore any leading tabs
 4712        editor.join_lines(&JoinLines, window, cx);
 4713        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 4714
 4715        editor
 4716    });
 4717}
 4718
 4719#[gpui::test]
 4720fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 4721    init_test(cx, |_| {});
 4722
 4723    cx.add_window(|window, cx| {
 4724        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4725        let mut editor = build_editor(buffer.clone(), window, cx);
 4726        let buffer = buffer.read(cx).as_singleton().unwrap();
 4727
 4728        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4729            s.select_ranges([
 4730                Point::new(0, 2)..Point::new(1, 1),
 4731                Point::new(1, 2)..Point::new(1, 2),
 4732                Point::new(3, 1)..Point::new(3, 2),
 4733            ])
 4734        });
 4735
 4736        editor.join_lines(&JoinLines, window, cx);
 4737        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 4738
 4739        assert_eq!(
 4740            editor
 4741                .selections
 4742                .ranges::<Point>(&editor.display_snapshot(cx)),
 4743            [
 4744                Point::new(0, 7)..Point::new(0, 7),
 4745                Point::new(1, 3)..Point::new(1, 3)
 4746            ]
 4747        );
 4748        editor
 4749    });
 4750}
 4751
 4752#[gpui::test]
 4753async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 4754    init_test(cx, |_| {});
 4755
 4756    let mut cx = EditorTestContext::new(cx).await;
 4757
 4758    let diff_base = r#"
 4759        Line 0
 4760        Line 1
 4761        Line 2
 4762        Line 3
 4763        "#
 4764    .unindent();
 4765
 4766    cx.set_state(
 4767        &r#"
 4768        ˇLine 0
 4769        Line 1
 4770        Line 2
 4771        Line 3
 4772        "#
 4773        .unindent(),
 4774    );
 4775
 4776    cx.set_head_text(&diff_base);
 4777    executor.run_until_parked();
 4778
 4779    // Join lines
 4780    cx.update_editor(|editor, window, cx| {
 4781        editor.join_lines(&JoinLines, window, cx);
 4782    });
 4783    executor.run_until_parked();
 4784
 4785    cx.assert_editor_state(
 4786        &r#"
 4787        Line 0ˇ Line 1
 4788        Line 2
 4789        Line 3
 4790        "#
 4791        .unindent(),
 4792    );
 4793    // Join again
 4794    cx.update_editor(|editor, window, cx| {
 4795        editor.join_lines(&JoinLines, window, cx);
 4796    });
 4797    executor.run_until_parked();
 4798
 4799    cx.assert_editor_state(
 4800        &r#"
 4801        Line 0 Line 1ˇ Line 2
 4802        Line 3
 4803        "#
 4804        .unindent(),
 4805    );
 4806}
 4807
 4808#[gpui::test]
 4809async fn test_custom_newlines_cause_no_false_positive_diffs(
 4810    executor: BackgroundExecutor,
 4811    cx: &mut TestAppContext,
 4812) {
 4813    init_test(cx, |_| {});
 4814    let mut cx = EditorTestContext::new(cx).await;
 4815    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 4816    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 4817    executor.run_until_parked();
 4818
 4819    cx.update_editor(|editor, window, cx| {
 4820        let snapshot = editor.snapshot(window, cx);
 4821        assert_eq!(
 4822            snapshot
 4823                .buffer_snapshot()
 4824                .diff_hunks_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
 4825                .collect::<Vec<_>>(),
 4826            Vec::new(),
 4827            "Should not have any diffs for files with custom newlines"
 4828        );
 4829    });
 4830}
 4831
 4832#[gpui::test]
 4833async fn test_manipulate_immutable_lines_with_single_selection(cx: &mut TestAppContext) {
 4834    init_test(cx, |_| {});
 4835
 4836    let mut cx = EditorTestContext::new(cx).await;
 4837
 4838    // Test sort_lines_case_insensitive()
 4839    cx.set_state(indoc! {"
 4840        «z
 4841        y
 4842        x
 4843        Z
 4844        Y
 4845        Xˇ»
 4846    "});
 4847    cx.update_editor(|e, window, cx| {
 4848        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 4849    });
 4850    cx.assert_editor_state(indoc! {"
 4851        «x
 4852        X
 4853        y
 4854        Y
 4855        z
 4856        Zˇ»
 4857    "});
 4858
 4859    // Test sort_lines_by_length()
 4860    //
 4861    // Demonstrates:
 4862    // - ∞ is 3 bytes UTF-8, but sorted by its char count (1)
 4863    // - sort is stable
 4864    cx.set_state(indoc! {"
 4865        «123
 4866        æ
 4867        12
 4868 4869        1
 4870        æˇ»
 4871    "});
 4872    cx.update_editor(|e, window, cx| e.sort_lines_by_length(&SortLinesByLength, window, cx));
 4873    cx.assert_editor_state(indoc! {"
 4874        «æ
 4875 4876        1
 4877        æ
 4878        12
 4879        123ˇ»
 4880    "});
 4881
 4882    // Test reverse_lines()
 4883    cx.set_state(indoc! {"
 4884        «5
 4885        4
 4886        3
 4887        2
 4888        1ˇ»
 4889    "});
 4890    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4891    cx.assert_editor_state(indoc! {"
 4892        «1
 4893        2
 4894        3
 4895        4
 4896        5ˇ»
 4897    "});
 4898
 4899    // Skip testing shuffle_line()
 4900
 4901    // From here on out, test more complex cases of manipulate_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 4902    // Since all methods calling manipulate_immutable_lines() are doing the exact same general thing (reordering lines)
 4903
 4904    // Don't manipulate when cursor is on single line, but expand the selection
 4905    cx.set_state(indoc! {"
 4906        ddˇdd
 4907        ccc
 4908        bb
 4909        a
 4910    "});
 4911    cx.update_editor(|e, window, cx| {
 4912        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4913    });
 4914    cx.assert_editor_state(indoc! {"
 4915        «ddddˇ»
 4916        ccc
 4917        bb
 4918        a
 4919    "});
 4920
 4921    // Basic manipulate case
 4922    // Start selection moves to column 0
 4923    // End of selection shrinks to fit shorter line
 4924    cx.set_state(indoc! {"
 4925        dd«d
 4926        ccc
 4927        bb
 4928        aaaaaˇ»
 4929    "});
 4930    cx.update_editor(|e, window, cx| {
 4931        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4932    });
 4933    cx.assert_editor_state(indoc! {"
 4934        «aaaaa
 4935        bb
 4936        ccc
 4937        dddˇ»
 4938    "});
 4939
 4940    // Manipulate case with newlines
 4941    cx.set_state(indoc! {"
 4942        dd«d
 4943        ccc
 4944
 4945        bb
 4946        aaaaa
 4947
 4948        ˇ»
 4949    "});
 4950    cx.update_editor(|e, window, cx| {
 4951        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4952    });
 4953    cx.assert_editor_state(indoc! {"
 4954        «
 4955
 4956        aaaaa
 4957        bb
 4958        ccc
 4959        dddˇ»
 4960
 4961    "});
 4962
 4963    // Adding new line
 4964    cx.set_state(indoc! {"
 4965        aa«a
 4966        bbˇ»b
 4967    "});
 4968    cx.update_editor(|e, window, cx| {
 4969        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added_line"))
 4970    });
 4971    cx.assert_editor_state(indoc! {"
 4972        «aaa
 4973        bbb
 4974        added_lineˇ»
 4975    "});
 4976
 4977    // Removing line
 4978    cx.set_state(indoc! {"
 4979        aa«a
 4980        bbbˇ»
 4981    "});
 4982    cx.update_editor(|e, window, cx| {
 4983        e.manipulate_immutable_lines(window, cx, |lines| {
 4984            lines.pop();
 4985        })
 4986    });
 4987    cx.assert_editor_state(indoc! {"
 4988        «aaaˇ»
 4989    "});
 4990
 4991    // Removing all lines
 4992    cx.set_state(indoc! {"
 4993        aa«a
 4994        bbbˇ»
 4995    "});
 4996    cx.update_editor(|e, window, cx| {
 4997        e.manipulate_immutable_lines(window, cx, |lines| {
 4998            lines.drain(..);
 4999        })
 5000    });
 5001    cx.assert_editor_state(indoc! {"
 5002        ˇ
 5003    "});
 5004}
 5005
 5006#[gpui::test]
 5007async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 5008    init_test(cx, |_| {});
 5009
 5010    let mut cx = EditorTestContext::new(cx).await;
 5011
 5012    // Consider continuous selection as single selection
 5013    cx.set_state(indoc! {"
 5014        Aaa«aa
 5015        cˇ»c«c
 5016        bb
 5017        aaaˇ»aa
 5018    "});
 5019    cx.update_editor(|e, window, cx| {
 5020        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 5021    });
 5022    cx.assert_editor_state(indoc! {"
 5023        «Aaaaa
 5024        ccc
 5025        bb
 5026        aaaaaˇ»
 5027    "});
 5028
 5029    cx.set_state(indoc! {"
 5030        Aaa«aa
 5031        cˇ»c«c
 5032        bb
 5033        aaaˇ»aa
 5034    "});
 5035    cx.update_editor(|e, window, cx| {
 5036        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 5037    });
 5038    cx.assert_editor_state(indoc! {"
 5039        «Aaaaa
 5040        ccc
 5041        bbˇ»
 5042    "});
 5043
 5044    // Consider non continuous selection as distinct dedup operations
 5045    cx.set_state(indoc! {"
 5046        «aaaaa
 5047        bb
 5048        aaaaa
 5049        aaaaaˇ»
 5050
 5051        aaa«aaˇ»
 5052    "});
 5053    cx.update_editor(|e, window, cx| {
 5054        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 5055    });
 5056    cx.assert_editor_state(indoc! {"
 5057        «aaaaa
 5058        bbˇ»
 5059
 5060        «aaaaaˇ»
 5061    "});
 5062}
 5063
 5064#[gpui::test]
 5065async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 5066    init_test(cx, |_| {});
 5067
 5068    let mut cx = EditorTestContext::new(cx).await;
 5069
 5070    cx.set_state(indoc! {"
 5071        «Aaa
 5072        aAa
 5073        Aaaˇ»
 5074    "});
 5075    cx.update_editor(|e, window, cx| {
 5076        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 5077    });
 5078    cx.assert_editor_state(indoc! {"
 5079        «Aaa
 5080        aAaˇ»
 5081    "});
 5082
 5083    cx.set_state(indoc! {"
 5084        «Aaa
 5085        aAa
 5086        aaAˇ»
 5087    "});
 5088    cx.update_editor(|e, window, cx| {
 5089        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 5090    });
 5091    cx.assert_editor_state(indoc! {"
 5092        «Aaaˇ»
 5093    "});
 5094}
 5095
 5096#[gpui::test]
 5097async fn test_wrap_in_tag_single_selection(cx: &mut TestAppContext) {
 5098    init_test(cx, |_| {});
 5099
 5100    let mut cx = EditorTestContext::new(cx).await;
 5101
 5102    let js_language = Arc::new(Language::new(
 5103        LanguageConfig {
 5104            name: "JavaScript".into(),
 5105            wrap_characters: Some(language::WrapCharactersConfig {
 5106                start_prefix: "<".into(),
 5107                start_suffix: ">".into(),
 5108                end_prefix: "</".into(),
 5109                end_suffix: ">".into(),
 5110            }),
 5111            ..LanguageConfig::default()
 5112        },
 5113        None,
 5114    ));
 5115
 5116    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 5117
 5118    cx.set_state(indoc! {"
 5119        «testˇ»
 5120    "});
 5121    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5122    cx.assert_editor_state(indoc! {"
 5123        <«ˇ»>test</«ˇ»>
 5124    "});
 5125
 5126    cx.set_state(indoc! {"
 5127        «test
 5128         testˇ»
 5129    "});
 5130    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5131    cx.assert_editor_state(indoc! {"
 5132        <«ˇ»>test
 5133         test</«ˇ»>
 5134    "});
 5135
 5136    cx.set_state(indoc! {"
 5137        teˇst
 5138    "});
 5139    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5140    cx.assert_editor_state(indoc! {"
 5141        te<«ˇ»></«ˇ»>st
 5142    "});
 5143}
 5144
 5145#[gpui::test]
 5146async fn test_wrap_in_tag_multi_selection(cx: &mut TestAppContext) {
 5147    init_test(cx, |_| {});
 5148
 5149    let mut cx = EditorTestContext::new(cx).await;
 5150
 5151    let js_language = Arc::new(Language::new(
 5152        LanguageConfig {
 5153            name: "JavaScript".into(),
 5154            wrap_characters: Some(language::WrapCharactersConfig {
 5155                start_prefix: "<".into(),
 5156                start_suffix: ">".into(),
 5157                end_prefix: "</".into(),
 5158                end_suffix: ">".into(),
 5159            }),
 5160            ..LanguageConfig::default()
 5161        },
 5162        None,
 5163    ));
 5164
 5165    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 5166
 5167    cx.set_state(indoc! {"
 5168        «testˇ»
 5169        «testˇ» «testˇ»
 5170        «testˇ»
 5171    "});
 5172    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5173    cx.assert_editor_state(indoc! {"
 5174        <«ˇ»>test</«ˇ»>
 5175        <«ˇ»>test</«ˇ»> <«ˇ»>test</«ˇ»>
 5176        <«ˇ»>test</«ˇ»>
 5177    "});
 5178
 5179    cx.set_state(indoc! {"
 5180        «test
 5181         testˇ»
 5182        «test
 5183         testˇ»
 5184    "});
 5185    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5186    cx.assert_editor_state(indoc! {"
 5187        <«ˇ»>test
 5188         test</«ˇ»>
 5189        <«ˇ»>test
 5190         test</«ˇ»>
 5191    "});
 5192}
 5193
 5194#[gpui::test]
 5195async fn test_wrap_in_tag_does_nothing_in_unsupported_languages(cx: &mut TestAppContext) {
 5196    init_test(cx, |_| {});
 5197
 5198    let mut cx = EditorTestContext::new(cx).await;
 5199
 5200    let plaintext_language = Arc::new(Language::new(
 5201        LanguageConfig {
 5202            name: "Plain Text".into(),
 5203            ..LanguageConfig::default()
 5204        },
 5205        None,
 5206    ));
 5207
 5208    cx.update_buffer(|buffer, cx| buffer.set_language(Some(plaintext_language), cx));
 5209
 5210    cx.set_state(indoc! {"
 5211        «testˇ»
 5212    "});
 5213    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5214    cx.assert_editor_state(indoc! {"
 5215      «testˇ»
 5216    "});
 5217}
 5218
 5219#[gpui::test]
 5220async fn test_manipulate_immutable_lines_with_multi_selection(cx: &mut TestAppContext) {
 5221    init_test(cx, |_| {});
 5222
 5223    let mut cx = EditorTestContext::new(cx).await;
 5224
 5225    // Manipulate with multiple selections on a single line
 5226    cx.set_state(indoc! {"
 5227        dd«dd
 5228        cˇ»c«c
 5229        bb
 5230        aaaˇ»aa
 5231    "});
 5232    cx.update_editor(|e, window, cx| {
 5233        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 5234    });
 5235    cx.assert_editor_state(indoc! {"
 5236        «aaaaa
 5237        bb
 5238        ccc
 5239        ddddˇ»
 5240    "});
 5241
 5242    // Manipulate with multiple disjoin selections
 5243    cx.set_state(indoc! {"
 5244 5245        4
 5246        3
 5247        2
 5248        1ˇ»
 5249
 5250        dd«dd
 5251        ccc
 5252        bb
 5253        aaaˇ»aa
 5254    "});
 5255    cx.update_editor(|e, window, cx| {
 5256        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 5257    });
 5258    cx.assert_editor_state(indoc! {"
 5259        «1
 5260        2
 5261        3
 5262        4
 5263        5ˇ»
 5264
 5265        «aaaaa
 5266        bb
 5267        ccc
 5268        ddddˇ»
 5269    "});
 5270
 5271    // Adding lines on each selection
 5272    cx.set_state(indoc! {"
 5273 5274        1ˇ»
 5275
 5276        bb«bb
 5277        aaaˇ»aa
 5278    "});
 5279    cx.update_editor(|e, window, cx| {
 5280        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added line"))
 5281    });
 5282    cx.assert_editor_state(indoc! {"
 5283        «2
 5284        1
 5285        added lineˇ»
 5286
 5287        «bbbb
 5288        aaaaa
 5289        added lineˇ»
 5290    "});
 5291
 5292    // Removing lines on each selection
 5293    cx.set_state(indoc! {"
 5294 5295        1ˇ»
 5296
 5297        bb«bb
 5298        aaaˇ»aa
 5299    "});
 5300    cx.update_editor(|e, window, cx| {
 5301        e.manipulate_immutable_lines(window, cx, |lines| {
 5302            lines.pop();
 5303        })
 5304    });
 5305    cx.assert_editor_state(indoc! {"
 5306        «2ˇ»
 5307
 5308        «bbbbˇ»
 5309    "});
 5310}
 5311
 5312#[gpui::test]
 5313async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 5314    init_test(cx, |settings| {
 5315        settings.defaults.tab_size = NonZeroU32::new(3)
 5316    });
 5317
 5318    let mut cx = EditorTestContext::new(cx).await;
 5319
 5320    // MULTI SELECTION
 5321    // Ln.1 "«" tests empty lines
 5322    // Ln.9 tests just leading whitespace
 5323    cx.set_state(indoc! {"
 5324        «
 5325        abc                 // No indentationˇ»
 5326        «\tabc              // 1 tabˇ»
 5327        \t\tabc «      ˇ»   // 2 tabs
 5328        \t ab«c             // Tab followed by space
 5329         \tabc              // Space followed by tab (3 spaces should be the result)
 5330        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5331           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 5332        \t
 5333        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5334    "});
 5335    cx.update_editor(|e, window, cx| {
 5336        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5337    });
 5338    cx.assert_editor_state(
 5339        indoc! {"
 5340            «
 5341            abc                 // No indentation
 5342               abc              // 1 tab
 5343                  abc          // 2 tabs
 5344                abc             // Tab followed by space
 5345               abc              // Space followed by tab (3 spaces should be the result)
 5346                           abc   // Mixed indentation (tab conversion depends on the column)
 5347               abc         // Already space indented
 5348               ·
 5349               abc\tdef          // Only the leading tab is manipulatedˇ»
 5350        "}
 5351        .replace("·", "")
 5352        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5353    );
 5354
 5355    // Test on just a few lines, the others should remain unchanged
 5356    // Only lines (3, 5, 10, 11) should change
 5357    cx.set_state(
 5358        indoc! {"
 5359            ·
 5360            abc                 // No indentation
 5361            \tabcˇ               // 1 tab
 5362            \t\tabc             // 2 tabs
 5363            \t abcˇ              // Tab followed by space
 5364             \tabc              // Space followed by tab (3 spaces should be the result)
 5365            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5366               abc              // Already space indented
 5367            «\t
 5368            \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5369        "}
 5370        .replace("·", "")
 5371        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5372    );
 5373    cx.update_editor(|e, window, cx| {
 5374        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5375    });
 5376    cx.assert_editor_state(
 5377        indoc! {"
 5378            ·
 5379            abc                 // No indentation
 5380            «   abc               // 1 tabˇ»
 5381            \t\tabc             // 2 tabs
 5382            «    abc              // Tab followed by spaceˇ»
 5383             \tabc              // Space followed by tab (3 spaces should be the result)
 5384            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5385               abc              // Already space indented
 5386            «   ·
 5387               abc\tdef          // Only the leading tab is manipulatedˇ»
 5388        "}
 5389        .replace("·", "")
 5390        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5391    );
 5392
 5393    // SINGLE SELECTION
 5394    // Ln.1 "«" tests empty lines
 5395    // Ln.9 tests just leading whitespace
 5396    cx.set_state(indoc! {"
 5397        «
 5398        abc                 // No indentation
 5399        \tabc               // 1 tab
 5400        \t\tabc             // 2 tabs
 5401        \t abc              // Tab followed by space
 5402         \tabc              // Space followed by tab (3 spaces should be the result)
 5403        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5404           abc              // Already space indented
 5405        \t
 5406        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5407    "});
 5408    cx.update_editor(|e, window, cx| {
 5409        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5410    });
 5411    cx.assert_editor_state(
 5412        indoc! {"
 5413            «
 5414            abc                 // No indentation
 5415               abc               // 1 tab
 5416                  abc             // 2 tabs
 5417                abc              // Tab followed by space
 5418               abc              // Space followed by tab (3 spaces should be the result)
 5419                           abc   // Mixed indentation (tab conversion depends on the column)
 5420               abc              // Already space indented
 5421               ·
 5422               abc\tdef          // Only the leading tab is manipulatedˇ»
 5423        "}
 5424        .replace("·", "")
 5425        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5426    );
 5427}
 5428
 5429#[gpui::test]
 5430async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 5431    init_test(cx, |settings| {
 5432        settings.defaults.tab_size = NonZeroU32::new(3)
 5433    });
 5434
 5435    let mut cx = EditorTestContext::new(cx).await;
 5436
 5437    // MULTI SELECTION
 5438    // Ln.1 "«" tests empty lines
 5439    // Ln.11 tests just leading whitespace
 5440    cx.set_state(indoc! {"
 5441        «
 5442        abˇ»ˇc                 // No indentation
 5443         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 5444          abc  «             // 2 spaces (< 3 so dont convert)
 5445           abc              // 3 spaces (convert)
 5446             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 5447        «\tˇ»\t«\tˇ»abc           // Already tab indented
 5448        «\t abc              // Tab followed by space
 5449         \tabc              // Space followed by tab (should be consumed due to tab)
 5450        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5451           \tˇ»  «\t
 5452           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 5453    "});
 5454    cx.update_editor(|e, window, cx| {
 5455        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5456    });
 5457    cx.assert_editor_state(indoc! {"
 5458        «
 5459        abc                 // No indentation
 5460         abc                // 1 space (< 3 so dont convert)
 5461          abc               // 2 spaces (< 3 so dont convert)
 5462        \tabc              // 3 spaces (convert)
 5463        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5464        \t\t\tabc           // Already tab indented
 5465        \t abc              // Tab followed by space
 5466        \tabc              // Space followed by tab (should be consumed due to tab)
 5467        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5468        \t\t\t
 5469        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5470    "});
 5471
 5472    // Test on just a few lines, the other should remain unchanged
 5473    // Only lines (4, 8, 11, 12) should change
 5474    cx.set_state(
 5475        indoc! {"
 5476            ·
 5477            abc                 // No indentation
 5478             abc                // 1 space (< 3 so dont convert)
 5479              abc               // 2 spaces (< 3 so dont convert)
 5480            «   abc              // 3 spaces (convert)ˇ»
 5481                 abc            // 5 spaces (1 tab + 2 spaces)
 5482            \t\t\tabc           // Already tab indented
 5483            \t abc              // Tab followed by space
 5484             \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 5485               \t\t  \tabc      // Mixed indentation
 5486            \t \t  \t   \tabc   // Mixed indentation
 5487               \t  \tˇ
 5488            «   abc   \t         // Only the leading spaces should be convertedˇ»
 5489        "}
 5490        .replace("·", "")
 5491        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5492    );
 5493    cx.update_editor(|e, window, cx| {
 5494        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5495    });
 5496    cx.assert_editor_state(
 5497        indoc! {"
 5498            ·
 5499            abc                 // No indentation
 5500             abc                // 1 space (< 3 so dont convert)
 5501              abc               // 2 spaces (< 3 so dont convert)
 5502            «\tabc              // 3 spaces (convert)ˇ»
 5503                 abc            // 5 spaces (1 tab + 2 spaces)
 5504            \t\t\tabc           // Already tab indented
 5505            \t abc              // Tab followed by space
 5506            «\tabc              // Space followed by tab (should be consumed due to tab)ˇ»
 5507               \t\t  \tabc      // Mixed indentation
 5508            \t \t  \t   \tabc   // Mixed indentation
 5509            «\t\t\t
 5510            \tabc   \t         // Only the leading spaces should be convertedˇ»
 5511        "}
 5512        .replace("·", "")
 5513        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5514    );
 5515
 5516    // SINGLE SELECTION
 5517    // Ln.1 "«" tests empty lines
 5518    // Ln.11 tests just leading whitespace
 5519    cx.set_state(indoc! {"
 5520        «
 5521        abc                 // No indentation
 5522         abc                // 1 space (< 3 so dont convert)
 5523          abc               // 2 spaces (< 3 so dont convert)
 5524           abc              // 3 spaces (convert)
 5525             abc            // 5 spaces (1 tab + 2 spaces)
 5526        \t\t\tabc           // Already tab indented
 5527        \t abc              // Tab followed by space
 5528         \tabc              // Space followed by tab (should be consumed due to tab)
 5529        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5530           \t  \t
 5531           abc   \t         // Only the leading spaces should be convertedˇ»
 5532    "});
 5533    cx.update_editor(|e, window, cx| {
 5534        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5535    });
 5536    cx.assert_editor_state(indoc! {"
 5537        «
 5538        abc                 // No indentation
 5539         abc                // 1 space (< 3 so dont convert)
 5540          abc               // 2 spaces (< 3 so dont convert)
 5541        \tabc              // 3 spaces (convert)
 5542        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5543        \t\t\tabc           // Already tab indented
 5544        \t abc              // Tab followed by space
 5545        \tabc              // Space followed by tab (should be consumed due to tab)
 5546        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5547        \t\t\t
 5548        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5549    "});
 5550}
 5551
 5552#[gpui::test]
 5553async fn test_toggle_case(cx: &mut TestAppContext) {
 5554    init_test(cx, |_| {});
 5555
 5556    let mut cx = EditorTestContext::new(cx).await;
 5557
 5558    // If all lower case -> upper case
 5559    cx.set_state(indoc! {"
 5560        «hello worldˇ»
 5561    "});
 5562    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5563    cx.assert_editor_state(indoc! {"
 5564        «HELLO WORLDˇ»
 5565    "});
 5566
 5567    // If all upper case -> lower case
 5568    cx.set_state(indoc! {"
 5569        «HELLO WORLDˇ»
 5570    "});
 5571    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5572    cx.assert_editor_state(indoc! {"
 5573        «hello worldˇ»
 5574    "});
 5575
 5576    // If any upper case characters are identified -> lower case
 5577    // This matches JetBrains IDEs
 5578    cx.set_state(indoc! {"
 5579        «hEllo worldˇ»
 5580    "});
 5581    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5582    cx.assert_editor_state(indoc! {"
 5583        «hello worldˇ»
 5584    "});
 5585}
 5586
 5587#[gpui::test]
 5588async fn test_convert_to_sentence_case(cx: &mut TestAppContext) {
 5589    init_test(cx, |_| {});
 5590
 5591    let mut cx = EditorTestContext::new(cx).await;
 5592
 5593    cx.set_state(indoc! {"
 5594        «implement-windows-supportˇ»
 5595    "});
 5596    cx.update_editor(|e, window, cx| {
 5597        e.convert_to_sentence_case(&ConvertToSentenceCase, window, cx)
 5598    });
 5599    cx.assert_editor_state(indoc! {"
 5600        «Implement windows supportˇ»
 5601    "});
 5602}
 5603
 5604#[gpui::test]
 5605async fn test_manipulate_text(cx: &mut TestAppContext) {
 5606    init_test(cx, |_| {});
 5607
 5608    let mut cx = EditorTestContext::new(cx).await;
 5609
 5610    // Test convert_to_upper_case()
 5611    cx.set_state(indoc! {"
 5612        «hello worldˇ»
 5613    "});
 5614    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5615    cx.assert_editor_state(indoc! {"
 5616        «HELLO WORLDˇ»
 5617    "});
 5618
 5619    // Test convert_to_lower_case()
 5620    cx.set_state(indoc! {"
 5621        «HELLO WORLDˇ»
 5622    "});
 5623    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 5624    cx.assert_editor_state(indoc! {"
 5625        «hello worldˇ»
 5626    "});
 5627
 5628    // Test multiple line, single selection case
 5629    cx.set_state(indoc! {"
 5630        «The quick brown
 5631        fox jumps over
 5632        the lazy dogˇ»
 5633    "});
 5634    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 5635    cx.assert_editor_state(indoc! {"
 5636        «The Quick Brown
 5637        Fox Jumps Over
 5638        The Lazy Dogˇ»
 5639    "});
 5640
 5641    // Test multiple line, single selection case
 5642    cx.set_state(indoc! {"
 5643        «The quick brown
 5644        fox jumps over
 5645        the lazy dogˇ»
 5646    "});
 5647    cx.update_editor(|e, window, cx| {
 5648        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 5649    });
 5650    cx.assert_editor_state(indoc! {"
 5651        «TheQuickBrown
 5652        FoxJumpsOver
 5653        TheLazyDogˇ»
 5654    "});
 5655
 5656    // From here on out, test more complex cases of manipulate_text()
 5657
 5658    // Test no selection case - should affect words cursors are in
 5659    // Cursor at beginning, middle, and end of word
 5660    cx.set_state(indoc! {"
 5661        ˇhello big beauˇtiful worldˇ
 5662    "});
 5663    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5664    cx.assert_editor_state(indoc! {"
 5665        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 5666    "});
 5667
 5668    // Test multiple selections on a single line and across multiple lines
 5669    cx.set_state(indoc! {"
 5670        «Theˇ» quick «brown
 5671        foxˇ» jumps «overˇ»
 5672        the «lazyˇ» dog
 5673    "});
 5674    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5675    cx.assert_editor_state(indoc! {"
 5676        «THEˇ» quick «BROWN
 5677        FOXˇ» jumps «OVERˇ»
 5678        the «LAZYˇ» dog
 5679    "});
 5680
 5681    // Test case where text length grows
 5682    cx.set_state(indoc! {"
 5683        «tschüߡ»
 5684    "});
 5685    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5686    cx.assert_editor_state(indoc! {"
 5687        «TSCHÜSSˇ»
 5688    "});
 5689
 5690    // Test to make sure we don't crash when text shrinks
 5691    cx.set_state(indoc! {"
 5692        aaa_bbbˇ
 5693    "});
 5694    cx.update_editor(|e, window, cx| {
 5695        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5696    });
 5697    cx.assert_editor_state(indoc! {"
 5698        «aaaBbbˇ»
 5699    "});
 5700
 5701    // Test to make sure we all aware of the fact that each word can grow and shrink
 5702    // Final selections should be aware of this fact
 5703    cx.set_state(indoc! {"
 5704        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 5705    "});
 5706    cx.update_editor(|e, window, cx| {
 5707        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5708    });
 5709    cx.assert_editor_state(indoc! {"
 5710        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 5711    "});
 5712
 5713    cx.set_state(indoc! {"
 5714        «hElLo, WoRld!ˇ»
 5715    "});
 5716    cx.update_editor(|e, window, cx| {
 5717        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 5718    });
 5719    cx.assert_editor_state(indoc! {"
 5720        «HeLlO, wOrLD!ˇ»
 5721    "});
 5722
 5723    // Test selections with `line_mode() = true`.
 5724    cx.update_editor(|editor, _window, _cx| editor.selections.set_line_mode(true));
 5725    cx.set_state(indoc! {"
 5726        «The quick brown
 5727        fox jumps over
 5728        tˇ»he lazy dog
 5729    "});
 5730    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5731    cx.assert_editor_state(indoc! {"
 5732        «THE QUICK BROWN
 5733        FOX JUMPS OVER
 5734        THE LAZY DOGˇ»
 5735    "});
 5736}
 5737
 5738#[gpui::test]
 5739fn test_duplicate_line(cx: &mut TestAppContext) {
 5740    init_test(cx, |_| {});
 5741
 5742    let editor = cx.add_window(|window, cx| {
 5743        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5744        build_editor(buffer, window, cx)
 5745    });
 5746    _ = editor.update(cx, |editor, window, cx| {
 5747        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5748            s.select_display_ranges([
 5749                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5750                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5751                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5752                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5753            ])
 5754        });
 5755        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5756        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5757        assert_eq!(
 5758            display_ranges(editor, cx),
 5759            vec![
 5760                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 5761                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 5762                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5763                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 5764            ]
 5765        );
 5766    });
 5767
 5768    let editor = cx.add_window(|window, cx| {
 5769        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5770        build_editor(buffer, window, cx)
 5771    });
 5772    _ = editor.update(cx, |editor, window, cx| {
 5773        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5774            s.select_display_ranges([
 5775                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5776                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5777            ])
 5778        });
 5779        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5780        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5781        assert_eq!(
 5782            display_ranges(editor, cx),
 5783            vec![
 5784                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 5785                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 5786            ]
 5787        );
 5788    });
 5789
 5790    // With `duplicate_line_up` the selections move to the duplicated lines,
 5791    // which are inserted above the original lines
 5792    let editor = cx.add_window(|window, cx| {
 5793        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5794        build_editor(buffer, window, cx)
 5795    });
 5796    _ = editor.update(cx, |editor, window, cx| {
 5797        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5798            s.select_display_ranges([
 5799                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5800                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5801                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5802                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5803            ])
 5804        });
 5805        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5806        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5807        assert_eq!(
 5808            display_ranges(editor, cx),
 5809            vec![
 5810                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5811                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5812                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 5813                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0),
 5814            ]
 5815        );
 5816    });
 5817
 5818    let editor = cx.add_window(|window, cx| {
 5819        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5820        build_editor(buffer, window, cx)
 5821    });
 5822    _ = editor.update(cx, |editor, window, cx| {
 5823        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5824            s.select_display_ranges([
 5825                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5826                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5827            ])
 5828        });
 5829        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5830        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5831        assert_eq!(
 5832            display_ranges(editor, cx),
 5833            vec![
 5834                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5835                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5836            ]
 5837        );
 5838    });
 5839
 5840    let editor = cx.add_window(|window, cx| {
 5841        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5842        build_editor(buffer, window, cx)
 5843    });
 5844    _ = editor.update(cx, |editor, window, cx| {
 5845        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5846            s.select_display_ranges([
 5847                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5848                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5849            ])
 5850        });
 5851        editor.duplicate_selection(&DuplicateSelection, window, cx);
 5852        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 5853        assert_eq!(
 5854            display_ranges(editor, cx),
 5855            vec![
 5856                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5857                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 5858            ]
 5859        );
 5860    });
 5861}
 5862
 5863#[gpui::test]
 5864async fn test_rotate_selections(cx: &mut TestAppContext) {
 5865    init_test(cx, |_| {});
 5866
 5867    let mut cx = EditorTestContext::new(cx).await;
 5868
 5869    // Rotate text selections (horizontal)
 5870    cx.set_state("x=«1ˇ», y=«2ˇ», z=«3ˇ»");
 5871    cx.update_editor(|e, window, cx| {
 5872        e.rotate_selections_forward(&RotateSelectionsForward, window, cx)
 5873    });
 5874    cx.assert_editor_state("x=«3ˇ», y=«1ˇ», z=«2ˇ»");
 5875    cx.update_editor(|e, window, cx| {
 5876        e.rotate_selections_backward(&RotateSelectionsBackward, window, cx)
 5877    });
 5878    cx.assert_editor_state("x=«1ˇ», y=«2ˇ», z=«3ˇ»");
 5879
 5880    // Rotate text selections (vertical)
 5881    cx.set_state(indoc! {"
 5882        x=«1ˇ»
 5883        y=«2ˇ»
 5884        z=«3ˇ»
 5885    "});
 5886    cx.update_editor(|e, window, cx| {
 5887        e.rotate_selections_forward(&RotateSelectionsForward, window, cx)
 5888    });
 5889    cx.assert_editor_state(indoc! {"
 5890        x=«3ˇ»
 5891        y=«1ˇ»
 5892        z=«2ˇ»
 5893    "});
 5894    cx.update_editor(|e, window, cx| {
 5895        e.rotate_selections_backward(&RotateSelectionsBackward, window, cx)
 5896    });
 5897    cx.assert_editor_state(indoc! {"
 5898        x=«1ˇ»
 5899        y=«2ˇ»
 5900        z=«3ˇ»
 5901    "});
 5902
 5903    // Rotate text selections (vertical, different lengths)
 5904    cx.set_state(indoc! {"
 5905        x=\"«ˇ»\"
 5906        y=\"«aˇ»\"
 5907        z=\"«aaˇ»\"
 5908    "});
 5909    cx.update_editor(|e, window, cx| {
 5910        e.rotate_selections_forward(&RotateSelectionsForward, window, cx)
 5911    });
 5912    cx.assert_editor_state(indoc! {"
 5913        x=\"«aaˇ»\"
 5914        y=\"«ˇ»\"
 5915        z=\"«aˇ»\"
 5916    "});
 5917    cx.update_editor(|e, window, cx| {
 5918        e.rotate_selections_backward(&RotateSelectionsBackward, window, cx)
 5919    });
 5920    cx.assert_editor_state(indoc! {"
 5921        x=\"«ˇ»\"
 5922        y=\"«aˇ»\"
 5923        z=\"«aaˇ»\"
 5924    "});
 5925
 5926    // Rotate whole lines (cursor positions preserved)
 5927    cx.set_state(indoc! {"
 5928        ˇline123
 5929        liˇne23
 5930        line3ˇ
 5931    "});
 5932    cx.update_editor(|e, window, cx| {
 5933        e.rotate_selections_forward(&RotateSelectionsForward, window, cx)
 5934    });
 5935    cx.assert_editor_state(indoc! {"
 5936        line3ˇ
 5937        ˇline123
 5938        liˇne23
 5939    "});
 5940    cx.update_editor(|e, window, cx| {
 5941        e.rotate_selections_backward(&RotateSelectionsBackward, window, cx)
 5942    });
 5943    cx.assert_editor_state(indoc! {"
 5944        ˇline123
 5945        liˇne23
 5946        line3ˇ
 5947    "});
 5948
 5949    // Rotate whole lines, multiple cursors per line (positions preserved)
 5950    cx.set_state(indoc! {"
 5951        ˇliˇne123
 5952        ˇline23
 5953        ˇline3
 5954    "});
 5955    cx.update_editor(|e, window, cx| {
 5956        e.rotate_selections_forward(&RotateSelectionsForward, window, cx)
 5957    });
 5958    cx.assert_editor_state(indoc! {"
 5959        ˇline3
 5960        ˇliˇne123
 5961        ˇline23
 5962    "});
 5963    cx.update_editor(|e, window, cx| {
 5964        e.rotate_selections_backward(&RotateSelectionsBackward, window, cx)
 5965    });
 5966    cx.assert_editor_state(indoc! {"
 5967        ˇliˇne123
 5968        ˇline23
 5969        ˇline3
 5970    "});
 5971}
 5972
 5973#[gpui::test]
 5974fn test_move_line_up_down(cx: &mut TestAppContext) {
 5975    init_test(cx, |_| {});
 5976
 5977    let editor = cx.add_window(|window, cx| {
 5978        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5979        build_editor(buffer, window, cx)
 5980    });
 5981    _ = editor.update(cx, |editor, window, cx| {
 5982        editor.fold_creases(
 5983            vec![
 5984                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 5985                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 5986                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 5987            ],
 5988            true,
 5989            window,
 5990            cx,
 5991        );
 5992        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5993            s.select_display_ranges([
 5994                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5995                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5996                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5997                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 5998            ])
 5999        });
 6000        assert_eq!(
 6001            editor.display_text(cx),
 6002            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 6003        );
 6004
 6005        editor.move_line_up(&MoveLineUp, window, cx);
 6006        assert_eq!(
 6007            editor.display_text(cx),
 6008            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 6009        );
 6010        assert_eq!(
 6011            display_ranges(editor, cx),
 6012            vec![
 6013                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 6014                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 6015                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 6016                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 6017            ]
 6018        );
 6019    });
 6020
 6021    _ = editor.update(cx, |editor, window, cx| {
 6022        editor.move_line_down(&MoveLineDown, window, cx);
 6023        assert_eq!(
 6024            editor.display_text(cx),
 6025            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 6026        );
 6027        assert_eq!(
 6028            display_ranges(editor, cx),
 6029            vec![
 6030                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 6031                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 6032                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 6033                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 6034            ]
 6035        );
 6036    });
 6037
 6038    _ = editor.update(cx, |editor, window, cx| {
 6039        editor.move_line_down(&MoveLineDown, window, cx);
 6040        assert_eq!(
 6041            editor.display_text(cx),
 6042            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 6043        );
 6044        assert_eq!(
 6045            display_ranges(editor, cx),
 6046            vec![
 6047                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 6048                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 6049                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 6050                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 6051            ]
 6052        );
 6053    });
 6054
 6055    _ = editor.update(cx, |editor, window, cx| {
 6056        editor.move_line_up(&MoveLineUp, window, cx);
 6057        assert_eq!(
 6058            editor.display_text(cx),
 6059            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 6060        );
 6061        assert_eq!(
 6062            display_ranges(editor, cx),
 6063            vec![
 6064                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 6065                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 6066                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 6067                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 6068            ]
 6069        );
 6070    });
 6071}
 6072
 6073#[gpui::test]
 6074fn test_move_line_up_selection_at_end_of_fold(cx: &mut TestAppContext) {
 6075    init_test(cx, |_| {});
 6076    let editor = cx.add_window(|window, cx| {
 6077        let buffer = MultiBuffer::build_simple("\n\n\n\n\n\naaaa\nbbbb\ncccc", cx);
 6078        build_editor(buffer, window, cx)
 6079    });
 6080    _ = editor.update(cx, |editor, window, cx| {
 6081        editor.fold_creases(
 6082            vec![Crease::simple(
 6083                Point::new(6, 4)..Point::new(7, 4),
 6084                FoldPlaceholder::test(),
 6085            )],
 6086            true,
 6087            window,
 6088            cx,
 6089        );
 6090        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6091            s.select_ranges([Point::new(7, 4)..Point::new(7, 4)])
 6092        });
 6093        assert_eq!(editor.display_text(cx), "\n\n\n\n\n\naaaa⋯\ncccc");
 6094        editor.move_line_up(&MoveLineUp, window, cx);
 6095        let buffer_text = editor.buffer.read(cx).snapshot(cx).text();
 6096        assert_eq!(buffer_text, "\n\n\n\n\naaaa\nbbbb\n\ncccc");
 6097    });
 6098}
 6099
 6100#[gpui::test]
 6101fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 6102    init_test(cx, |_| {});
 6103
 6104    let editor = cx.add_window(|window, cx| {
 6105        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 6106        build_editor(buffer, window, cx)
 6107    });
 6108    _ = editor.update(cx, |editor, window, cx| {
 6109        let snapshot = editor.buffer.read(cx).snapshot(cx);
 6110        editor.insert_blocks(
 6111            [BlockProperties {
 6112                style: BlockStyle::Fixed,
 6113                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 6114                height: Some(1),
 6115                render: Arc::new(|_| div().into_any()),
 6116                priority: 0,
 6117            }],
 6118            Some(Autoscroll::fit()),
 6119            cx,
 6120        );
 6121        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6122            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 6123        });
 6124        editor.move_line_down(&MoveLineDown, window, cx);
 6125    });
 6126}
 6127
 6128#[gpui::test]
 6129async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 6130    init_test(cx, |_| {});
 6131
 6132    let mut cx = EditorTestContext::new(cx).await;
 6133    cx.set_state(
 6134        &"
 6135            ˇzero
 6136            one
 6137            two
 6138            three
 6139            four
 6140            five
 6141        "
 6142        .unindent(),
 6143    );
 6144
 6145    // Create a four-line block that replaces three lines of text.
 6146    cx.update_editor(|editor, window, cx| {
 6147        let snapshot = editor.snapshot(window, cx);
 6148        let snapshot = &snapshot.buffer_snapshot();
 6149        let placement = BlockPlacement::Replace(
 6150            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 6151        );
 6152        editor.insert_blocks(
 6153            [BlockProperties {
 6154                placement,
 6155                height: Some(4),
 6156                style: BlockStyle::Sticky,
 6157                render: Arc::new(|_| gpui::div().into_any_element()),
 6158                priority: 0,
 6159            }],
 6160            None,
 6161            cx,
 6162        );
 6163    });
 6164
 6165    // Move down so that the cursor touches the block.
 6166    cx.update_editor(|editor, window, cx| {
 6167        editor.move_down(&Default::default(), window, cx);
 6168    });
 6169    cx.assert_editor_state(
 6170        &"
 6171            zero
 6172            «one
 6173            two
 6174            threeˇ»
 6175            four
 6176            five
 6177        "
 6178        .unindent(),
 6179    );
 6180
 6181    // Move down past the block.
 6182    cx.update_editor(|editor, window, cx| {
 6183        editor.move_down(&Default::default(), window, cx);
 6184    });
 6185    cx.assert_editor_state(
 6186        &"
 6187            zero
 6188            one
 6189            two
 6190            three
 6191            ˇfour
 6192            five
 6193        "
 6194        .unindent(),
 6195    );
 6196}
 6197
 6198#[gpui::test]
 6199fn test_transpose(cx: &mut TestAppContext) {
 6200    init_test(cx, |_| {});
 6201
 6202    _ = cx.add_window(|window, cx| {
 6203        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 6204        editor.set_style(EditorStyle::default(), window, cx);
 6205        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6206            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
 6207        });
 6208        editor.transpose(&Default::default(), window, cx);
 6209        assert_eq!(editor.text(cx), "bac");
 6210        assert_eq!(
 6211            editor.selections.ranges(&editor.display_snapshot(cx)),
 6212            [MultiBufferOffset(2)..MultiBufferOffset(2)]
 6213        );
 6214
 6215        editor.transpose(&Default::default(), window, cx);
 6216        assert_eq!(editor.text(cx), "bca");
 6217        assert_eq!(
 6218            editor.selections.ranges(&editor.display_snapshot(cx)),
 6219            [MultiBufferOffset(3)..MultiBufferOffset(3)]
 6220        );
 6221
 6222        editor.transpose(&Default::default(), window, cx);
 6223        assert_eq!(editor.text(cx), "bac");
 6224        assert_eq!(
 6225            editor.selections.ranges(&editor.display_snapshot(cx)),
 6226            [MultiBufferOffset(3)..MultiBufferOffset(3)]
 6227        );
 6228
 6229        editor
 6230    });
 6231
 6232    _ = cx.add_window(|window, cx| {
 6233        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 6234        editor.set_style(EditorStyle::default(), window, cx);
 6235        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6236            s.select_ranges([MultiBufferOffset(3)..MultiBufferOffset(3)])
 6237        });
 6238        editor.transpose(&Default::default(), window, cx);
 6239        assert_eq!(editor.text(cx), "acb\nde");
 6240        assert_eq!(
 6241            editor.selections.ranges(&editor.display_snapshot(cx)),
 6242            [MultiBufferOffset(3)..MultiBufferOffset(3)]
 6243        );
 6244
 6245        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6246            s.select_ranges([MultiBufferOffset(4)..MultiBufferOffset(4)])
 6247        });
 6248        editor.transpose(&Default::default(), window, cx);
 6249        assert_eq!(editor.text(cx), "acbd\ne");
 6250        assert_eq!(
 6251            editor.selections.ranges(&editor.display_snapshot(cx)),
 6252            [MultiBufferOffset(5)..MultiBufferOffset(5)]
 6253        );
 6254
 6255        editor.transpose(&Default::default(), window, cx);
 6256        assert_eq!(editor.text(cx), "acbde\n");
 6257        assert_eq!(
 6258            editor.selections.ranges(&editor.display_snapshot(cx)),
 6259            [MultiBufferOffset(6)..MultiBufferOffset(6)]
 6260        );
 6261
 6262        editor.transpose(&Default::default(), window, cx);
 6263        assert_eq!(editor.text(cx), "acbd\ne");
 6264        assert_eq!(
 6265            editor.selections.ranges(&editor.display_snapshot(cx)),
 6266            [MultiBufferOffset(6)..MultiBufferOffset(6)]
 6267        );
 6268
 6269        editor
 6270    });
 6271
 6272    _ = cx.add_window(|window, cx| {
 6273        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 6274        editor.set_style(EditorStyle::default(), window, cx);
 6275        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6276            s.select_ranges([
 6277                MultiBufferOffset(1)..MultiBufferOffset(1),
 6278                MultiBufferOffset(2)..MultiBufferOffset(2),
 6279                MultiBufferOffset(4)..MultiBufferOffset(4),
 6280            ])
 6281        });
 6282        editor.transpose(&Default::default(), window, cx);
 6283        assert_eq!(editor.text(cx), "bacd\ne");
 6284        assert_eq!(
 6285            editor.selections.ranges(&editor.display_snapshot(cx)),
 6286            [
 6287                MultiBufferOffset(2)..MultiBufferOffset(2),
 6288                MultiBufferOffset(3)..MultiBufferOffset(3),
 6289                MultiBufferOffset(5)..MultiBufferOffset(5)
 6290            ]
 6291        );
 6292
 6293        editor.transpose(&Default::default(), window, cx);
 6294        assert_eq!(editor.text(cx), "bcade\n");
 6295        assert_eq!(
 6296            editor.selections.ranges(&editor.display_snapshot(cx)),
 6297            [
 6298                MultiBufferOffset(3)..MultiBufferOffset(3),
 6299                MultiBufferOffset(4)..MultiBufferOffset(4),
 6300                MultiBufferOffset(6)..MultiBufferOffset(6)
 6301            ]
 6302        );
 6303
 6304        editor.transpose(&Default::default(), window, cx);
 6305        assert_eq!(editor.text(cx), "bcda\ne");
 6306        assert_eq!(
 6307            editor.selections.ranges(&editor.display_snapshot(cx)),
 6308            [
 6309                MultiBufferOffset(4)..MultiBufferOffset(4),
 6310                MultiBufferOffset(6)..MultiBufferOffset(6)
 6311            ]
 6312        );
 6313
 6314        editor.transpose(&Default::default(), window, cx);
 6315        assert_eq!(editor.text(cx), "bcade\n");
 6316        assert_eq!(
 6317            editor.selections.ranges(&editor.display_snapshot(cx)),
 6318            [
 6319                MultiBufferOffset(4)..MultiBufferOffset(4),
 6320                MultiBufferOffset(6)..MultiBufferOffset(6)
 6321            ]
 6322        );
 6323
 6324        editor.transpose(&Default::default(), window, cx);
 6325        assert_eq!(editor.text(cx), "bcaed\n");
 6326        assert_eq!(
 6327            editor.selections.ranges(&editor.display_snapshot(cx)),
 6328            [
 6329                MultiBufferOffset(5)..MultiBufferOffset(5),
 6330                MultiBufferOffset(6)..MultiBufferOffset(6)
 6331            ]
 6332        );
 6333
 6334        editor
 6335    });
 6336
 6337    _ = cx.add_window(|window, cx| {
 6338        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 6339        editor.set_style(EditorStyle::default(), window, cx);
 6340        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6341            s.select_ranges([MultiBufferOffset(4)..MultiBufferOffset(4)])
 6342        });
 6343        editor.transpose(&Default::default(), window, cx);
 6344        assert_eq!(editor.text(cx), "🏀🍐✋");
 6345        assert_eq!(
 6346            editor.selections.ranges(&editor.display_snapshot(cx)),
 6347            [MultiBufferOffset(8)..MultiBufferOffset(8)]
 6348        );
 6349
 6350        editor.transpose(&Default::default(), window, cx);
 6351        assert_eq!(editor.text(cx), "🏀✋🍐");
 6352        assert_eq!(
 6353            editor.selections.ranges(&editor.display_snapshot(cx)),
 6354            [MultiBufferOffset(11)..MultiBufferOffset(11)]
 6355        );
 6356
 6357        editor.transpose(&Default::default(), window, cx);
 6358        assert_eq!(editor.text(cx), "🏀🍐✋");
 6359        assert_eq!(
 6360            editor.selections.ranges(&editor.display_snapshot(cx)),
 6361            [MultiBufferOffset(11)..MultiBufferOffset(11)]
 6362        );
 6363
 6364        editor
 6365    });
 6366}
 6367
 6368#[gpui::test]
 6369async fn test_rewrap(cx: &mut TestAppContext) {
 6370    init_test(cx, |settings| {
 6371        settings.languages.0.extend([
 6372            (
 6373                "Markdown".into(),
 6374                LanguageSettingsContent {
 6375                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 6376                    preferred_line_length: Some(40),
 6377                    ..Default::default()
 6378                },
 6379            ),
 6380            (
 6381                "Plain Text".into(),
 6382                LanguageSettingsContent {
 6383                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 6384                    preferred_line_length: Some(40),
 6385                    ..Default::default()
 6386                },
 6387            ),
 6388            (
 6389                "C++".into(),
 6390                LanguageSettingsContent {
 6391                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6392                    preferred_line_length: Some(40),
 6393                    ..Default::default()
 6394                },
 6395            ),
 6396            (
 6397                "Python".into(),
 6398                LanguageSettingsContent {
 6399                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6400                    preferred_line_length: Some(40),
 6401                    ..Default::default()
 6402                },
 6403            ),
 6404            (
 6405                "Rust".into(),
 6406                LanguageSettingsContent {
 6407                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6408                    preferred_line_length: Some(40),
 6409                    ..Default::default()
 6410                },
 6411            ),
 6412        ])
 6413    });
 6414
 6415    let mut cx = EditorTestContext::new(cx).await;
 6416
 6417    let cpp_language = Arc::new(Language::new(
 6418        LanguageConfig {
 6419            name: "C++".into(),
 6420            line_comments: vec!["// ".into()],
 6421            ..LanguageConfig::default()
 6422        },
 6423        None,
 6424    ));
 6425    let python_language = Arc::new(Language::new(
 6426        LanguageConfig {
 6427            name: "Python".into(),
 6428            line_comments: vec!["# ".into()],
 6429            ..LanguageConfig::default()
 6430        },
 6431        None,
 6432    ));
 6433    let markdown_language = Arc::new(Language::new(
 6434        LanguageConfig {
 6435            name: "Markdown".into(),
 6436            rewrap_prefixes: vec![
 6437                regex::Regex::new("\\d+\\.\\s+").unwrap(),
 6438                regex::Regex::new("[-*+]\\s+").unwrap(),
 6439            ],
 6440            ..LanguageConfig::default()
 6441        },
 6442        None,
 6443    ));
 6444    let rust_language = Arc::new(
 6445        Language::new(
 6446            LanguageConfig {
 6447                name: "Rust".into(),
 6448                line_comments: vec!["// ".into(), "/// ".into()],
 6449                ..LanguageConfig::default()
 6450            },
 6451            Some(tree_sitter_rust::LANGUAGE.into()),
 6452        )
 6453        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 6454        .unwrap(),
 6455    );
 6456
 6457    let plaintext_language = Arc::new(Language::new(
 6458        LanguageConfig {
 6459            name: "Plain Text".into(),
 6460            ..LanguageConfig::default()
 6461        },
 6462        None,
 6463    ));
 6464
 6465    // Test basic rewrapping of a long line with a cursor
 6466    assert_rewrap(
 6467        indoc! {"
 6468            // ˇThis is a long comment that needs to be wrapped.
 6469        "},
 6470        indoc! {"
 6471            // ˇThis is a long comment that needs to
 6472            // be wrapped.
 6473        "},
 6474        cpp_language.clone(),
 6475        &mut cx,
 6476    );
 6477
 6478    // Test rewrapping a full selection
 6479    assert_rewrap(
 6480        indoc! {"
 6481            «// This selected long comment needs to be wrapped.ˇ»"
 6482        },
 6483        indoc! {"
 6484            «// This selected long comment needs to
 6485            // be wrapped.ˇ»"
 6486        },
 6487        cpp_language.clone(),
 6488        &mut cx,
 6489    );
 6490
 6491    // Test multiple cursors on different lines within the same paragraph are preserved after rewrapping
 6492    assert_rewrap(
 6493        indoc! {"
 6494            // ˇThis is the first line.
 6495            // Thisˇ is the second line.
 6496            // This is the thirdˇ line, all part of one paragraph.
 6497         "},
 6498        indoc! {"
 6499            // ˇThis is the first line. Thisˇ is the
 6500            // second line. This is the thirdˇ line,
 6501            // all part of one paragraph.
 6502         "},
 6503        cpp_language.clone(),
 6504        &mut cx,
 6505    );
 6506
 6507    // Test multiple cursors in different paragraphs trigger separate rewraps
 6508    assert_rewrap(
 6509        indoc! {"
 6510            // ˇThis is the first paragraph, first line.
 6511            // ˇThis is the first paragraph, second line.
 6512
 6513            // ˇThis is the second paragraph, first line.
 6514            // ˇThis is the second paragraph, second line.
 6515        "},
 6516        indoc! {"
 6517            // ˇThis is the first paragraph, first
 6518            // line. ˇThis is the first paragraph,
 6519            // second line.
 6520
 6521            // ˇThis is the second paragraph, first
 6522            // line. ˇThis is the second paragraph,
 6523            // second line.
 6524        "},
 6525        cpp_language.clone(),
 6526        &mut cx,
 6527    );
 6528
 6529    // Test that change in comment prefix (e.g., `//` to `///`) trigger seperate rewraps
 6530    assert_rewrap(
 6531        indoc! {"
 6532            «// A regular long long comment to be wrapped.
 6533            /// A documentation long comment to be wrapped.ˇ»
 6534          "},
 6535        indoc! {"
 6536            «// A regular long long comment to be
 6537            // wrapped.
 6538            /// A documentation long comment to be
 6539            /// wrapped.ˇ»
 6540          "},
 6541        rust_language.clone(),
 6542        &mut cx,
 6543    );
 6544
 6545    // Test that change in indentation level trigger seperate rewraps
 6546    assert_rewrap(
 6547        indoc! {"
 6548            fn foo() {
 6549                «// This is a long comment at the base indent.
 6550                    // This is a long comment at the next indent.ˇ»
 6551            }
 6552        "},
 6553        indoc! {"
 6554            fn foo() {
 6555                «// This is a long comment at the
 6556                // base indent.
 6557                    // This is a long comment at the
 6558                    // next indent.ˇ»
 6559            }
 6560        "},
 6561        rust_language.clone(),
 6562        &mut cx,
 6563    );
 6564
 6565    // Test that different comment prefix characters (e.g., '#') are handled correctly
 6566    assert_rewrap(
 6567        indoc! {"
 6568            # ˇThis is a long comment using a pound sign.
 6569        "},
 6570        indoc! {"
 6571            # ˇThis is a long comment using a pound
 6572            # sign.
 6573        "},
 6574        python_language,
 6575        &mut cx,
 6576    );
 6577
 6578    // Test rewrapping only affects comments, not code even when selected
 6579    assert_rewrap(
 6580        indoc! {"
 6581            «/// This doc comment is long and should be wrapped.
 6582            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6583        "},
 6584        indoc! {"
 6585            «/// This doc comment is long and should
 6586            /// be wrapped.
 6587            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6588        "},
 6589        rust_language.clone(),
 6590        &mut cx,
 6591    );
 6592
 6593    // Test that rewrapping works in Markdown documents where `allow_rewrap` is `Anywhere`
 6594    assert_rewrap(
 6595        indoc! {"
 6596            # Header
 6597
 6598            A long long long line of markdown text to wrap.ˇ
 6599         "},
 6600        indoc! {"
 6601            # Header
 6602
 6603            A long long long line of markdown text
 6604            to wrap.ˇ
 6605         "},
 6606        markdown_language.clone(),
 6607        &mut cx,
 6608    );
 6609
 6610    // Test that rewrapping boundary works and preserves relative indent for Markdown documents
 6611    assert_rewrap(
 6612        indoc! {"
 6613            «1. This is a numbered list item that is very long and needs to be wrapped properly.
 6614            2. This is a numbered list item that is very long and needs to be wrapped properly.
 6615            - This is an unordered list item that is also very long and should not merge with the numbered item.ˇ»
 6616        "},
 6617        indoc! {"
 6618            «1. This is a numbered list item that is
 6619               very long and needs to be wrapped
 6620               properly.
 6621            2. This is a numbered list item that is
 6622               very long and needs to be wrapped
 6623               properly.
 6624            - This is an unordered list item that is
 6625              also very long and should not merge
 6626              with the numbered item.ˇ»
 6627        "},
 6628        markdown_language.clone(),
 6629        &mut cx,
 6630    );
 6631
 6632    // Test that rewrapping add indents for rewrapping boundary if not exists already.
 6633    assert_rewrap(
 6634        indoc! {"
 6635            «1. This is a numbered list item that is
 6636            very long and needs to be wrapped
 6637            properly.
 6638            2. This is a numbered list item that is
 6639            very long and needs to be wrapped
 6640            properly.
 6641            - This is an unordered list item that is
 6642            also very long and should not merge with
 6643            the numbered item.ˇ»
 6644        "},
 6645        indoc! {"
 6646            «1. This is a numbered list item that is
 6647               very long and needs to be wrapped
 6648               properly.
 6649            2. This is a numbered list item that is
 6650               very long and needs to be wrapped
 6651               properly.
 6652            - This is an unordered list item that is
 6653              also very long and should not merge
 6654              with the numbered item.ˇ»
 6655        "},
 6656        markdown_language.clone(),
 6657        &mut cx,
 6658    );
 6659
 6660    // Test that rewrapping maintain indents even when they already exists.
 6661    assert_rewrap(
 6662        indoc! {"
 6663            «1. This is a numbered list
 6664               item that is very long and needs to be wrapped properly.
 6665            2. This is a numbered list
 6666               item that is very long and needs to be wrapped properly.
 6667            - This is an unordered list item that is also very long and
 6668              should not merge with the numbered item.ˇ»
 6669        "},
 6670        indoc! {"
 6671            «1. This is a numbered list item that is
 6672               very long and needs to be wrapped
 6673               properly.
 6674            2. This is a numbered list item that is
 6675               very long and needs to be wrapped
 6676               properly.
 6677            - This is an unordered list item that is
 6678              also very long and should not merge
 6679              with the numbered item.ˇ»
 6680        "},
 6681        markdown_language,
 6682        &mut cx,
 6683    );
 6684
 6685    // Test that rewrapping works in plain text where `allow_rewrap` is `Anywhere`
 6686    assert_rewrap(
 6687        indoc! {"
 6688            ˇThis is a very long line of plain text that will be wrapped.
 6689        "},
 6690        indoc! {"
 6691            ˇThis is a very long line of plain text
 6692            that will be wrapped.
 6693        "},
 6694        plaintext_language.clone(),
 6695        &mut cx,
 6696    );
 6697
 6698    // Test that non-commented code acts as a paragraph boundary within a selection
 6699    assert_rewrap(
 6700        indoc! {"
 6701               «// This is the first long comment block to be wrapped.
 6702               fn my_func(a: u32);
 6703               // This is the second long comment block to be wrapped.ˇ»
 6704           "},
 6705        indoc! {"
 6706               «// This is the first long comment block
 6707               // to be wrapped.
 6708               fn my_func(a: u32);
 6709               // This is the second long comment block
 6710               // to be wrapped.ˇ»
 6711           "},
 6712        rust_language,
 6713        &mut cx,
 6714    );
 6715
 6716    // Test rewrapping multiple selections, including ones with blank lines or tabs
 6717    assert_rewrap(
 6718        indoc! {"
 6719            «ˇThis is a very long line that will be wrapped.
 6720
 6721            This is another paragraph in the same selection.»
 6722
 6723            «\tThis is a very long indented line that will be wrapped.ˇ»
 6724         "},
 6725        indoc! {"
 6726            «ˇThis is a very long line that will be
 6727            wrapped.
 6728
 6729            This is another paragraph in the same
 6730            selection.»
 6731
 6732            «\tThis is a very long indented line
 6733            \tthat will be wrapped.ˇ»
 6734         "},
 6735        plaintext_language,
 6736        &mut cx,
 6737    );
 6738
 6739    // Test that an empty comment line acts as a paragraph boundary
 6740    assert_rewrap(
 6741        indoc! {"
 6742            // ˇThis is a long comment that will be wrapped.
 6743            //
 6744            // And this is another long comment that will also be wrapped.ˇ
 6745         "},
 6746        indoc! {"
 6747            // ˇThis is a long comment that will be
 6748            // wrapped.
 6749            //
 6750            // And this is another long comment that
 6751            // will also be wrapped.ˇ
 6752         "},
 6753        cpp_language,
 6754        &mut cx,
 6755    );
 6756
 6757    #[track_caller]
 6758    fn assert_rewrap(
 6759        unwrapped_text: &str,
 6760        wrapped_text: &str,
 6761        language: Arc<Language>,
 6762        cx: &mut EditorTestContext,
 6763    ) {
 6764        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6765        cx.set_state(unwrapped_text);
 6766        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6767        cx.assert_editor_state(wrapped_text);
 6768    }
 6769}
 6770
 6771#[gpui::test]
 6772async fn test_rewrap_block_comments(cx: &mut TestAppContext) {
 6773    init_test(cx, |settings| {
 6774        settings.languages.0.extend([(
 6775            "Rust".into(),
 6776            LanguageSettingsContent {
 6777                allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6778                preferred_line_length: Some(40),
 6779                ..Default::default()
 6780            },
 6781        )])
 6782    });
 6783
 6784    let mut cx = EditorTestContext::new(cx).await;
 6785
 6786    let rust_lang = Arc::new(
 6787        Language::new(
 6788            LanguageConfig {
 6789                name: "Rust".into(),
 6790                line_comments: vec!["// ".into()],
 6791                block_comment: Some(BlockCommentConfig {
 6792                    start: "/*".into(),
 6793                    end: "*/".into(),
 6794                    prefix: "* ".into(),
 6795                    tab_size: 1,
 6796                }),
 6797                documentation_comment: Some(BlockCommentConfig {
 6798                    start: "/**".into(),
 6799                    end: "*/".into(),
 6800                    prefix: "* ".into(),
 6801                    tab_size: 1,
 6802                }),
 6803
 6804                ..LanguageConfig::default()
 6805            },
 6806            Some(tree_sitter_rust::LANGUAGE.into()),
 6807        )
 6808        .with_override_query("[(line_comment) (block_comment)] @comment.inclusive")
 6809        .unwrap(),
 6810    );
 6811
 6812    // regular block comment
 6813    assert_rewrap(
 6814        indoc! {"
 6815            /*
 6816             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6817             */
 6818            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6819        "},
 6820        indoc! {"
 6821            /*
 6822             *ˇ Lorem ipsum dolor sit amet,
 6823             * consectetur adipiscing elit.
 6824             */
 6825            /*
 6826             *ˇ Lorem ipsum dolor sit amet,
 6827             * consectetur adipiscing elit.
 6828             */
 6829        "},
 6830        rust_lang.clone(),
 6831        &mut cx,
 6832    );
 6833
 6834    // indent is respected
 6835    assert_rewrap(
 6836        indoc! {"
 6837            {}
 6838                /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6839        "},
 6840        indoc! {"
 6841            {}
 6842                /*
 6843                 *ˇ Lorem ipsum dolor sit amet,
 6844                 * consectetur adipiscing elit.
 6845                 */
 6846        "},
 6847        rust_lang.clone(),
 6848        &mut cx,
 6849    );
 6850
 6851    // short block comments with inline delimiters
 6852    assert_rewrap(
 6853        indoc! {"
 6854            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6855            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6856             */
 6857            /*
 6858             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6859        "},
 6860        indoc! {"
 6861            /*
 6862             *ˇ Lorem ipsum dolor sit amet,
 6863             * consectetur adipiscing elit.
 6864             */
 6865            /*
 6866             *ˇ Lorem ipsum dolor sit amet,
 6867             * consectetur adipiscing elit.
 6868             */
 6869            /*
 6870             *ˇ Lorem ipsum dolor sit amet,
 6871             * consectetur adipiscing elit.
 6872             */
 6873        "},
 6874        rust_lang.clone(),
 6875        &mut cx,
 6876    );
 6877
 6878    // multiline block comment with inline start/end delimiters
 6879    assert_rewrap(
 6880        indoc! {"
 6881            /*ˇ Lorem ipsum dolor sit amet,
 6882             * consectetur adipiscing elit. */
 6883        "},
 6884        indoc! {"
 6885            /*
 6886             *ˇ Lorem ipsum dolor sit amet,
 6887             * consectetur adipiscing elit.
 6888             */
 6889        "},
 6890        rust_lang.clone(),
 6891        &mut cx,
 6892    );
 6893
 6894    // block comment rewrap still respects paragraph bounds
 6895    assert_rewrap(
 6896        indoc! {"
 6897            /*
 6898             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6899             *
 6900             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6901             */
 6902        "},
 6903        indoc! {"
 6904            /*
 6905             *ˇ Lorem ipsum dolor sit amet,
 6906             * consectetur adipiscing elit.
 6907             *
 6908             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6909             */
 6910        "},
 6911        rust_lang.clone(),
 6912        &mut cx,
 6913    );
 6914
 6915    // documentation comments
 6916    assert_rewrap(
 6917        indoc! {"
 6918            /**ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6919            /**
 6920             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6921             */
 6922        "},
 6923        indoc! {"
 6924            /**
 6925             *ˇ Lorem ipsum dolor sit amet,
 6926             * consectetur adipiscing elit.
 6927             */
 6928            /**
 6929             *ˇ Lorem ipsum dolor sit amet,
 6930             * consectetur adipiscing elit.
 6931             */
 6932        "},
 6933        rust_lang.clone(),
 6934        &mut cx,
 6935    );
 6936
 6937    // different, adjacent comments
 6938    assert_rewrap(
 6939        indoc! {"
 6940            /**
 6941             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6942             */
 6943            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6944            //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6945        "},
 6946        indoc! {"
 6947            /**
 6948             *ˇ Lorem ipsum dolor sit amet,
 6949             * consectetur adipiscing elit.
 6950             */
 6951            /*
 6952             *ˇ Lorem ipsum dolor sit amet,
 6953             * consectetur adipiscing elit.
 6954             */
 6955            //ˇ Lorem ipsum dolor sit amet,
 6956            // consectetur adipiscing elit.
 6957        "},
 6958        rust_lang.clone(),
 6959        &mut cx,
 6960    );
 6961
 6962    // selection w/ single short block comment
 6963    assert_rewrap(
 6964        indoc! {"
 6965            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6966        "},
 6967        indoc! {"
 6968            «/*
 6969             * Lorem ipsum dolor sit amet,
 6970             * consectetur adipiscing elit.
 6971             */ˇ»
 6972        "},
 6973        rust_lang.clone(),
 6974        &mut cx,
 6975    );
 6976
 6977    // rewrapping a single comment w/ abutting comments
 6978    assert_rewrap(
 6979        indoc! {"
 6980            /* ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6981            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6982        "},
 6983        indoc! {"
 6984            /*
 6985             * ˇLorem ipsum dolor sit amet,
 6986             * consectetur adipiscing elit.
 6987             */
 6988            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6989        "},
 6990        rust_lang.clone(),
 6991        &mut cx,
 6992    );
 6993
 6994    // selection w/ non-abutting short block comments
 6995    assert_rewrap(
 6996        indoc! {"
 6997            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6998
 6999            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 7000        "},
 7001        indoc! {"
 7002            «/*
 7003             * Lorem ipsum dolor sit amet,
 7004             * consectetur adipiscing elit.
 7005             */
 7006
 7007            /*
 7008             * Lorem ipsum dolor sit amet,
 7009             * consectetur adipiscing elit.
 7010             */ˇ»
 7011        "},
 7012        rust_lang.clone(),
 7013        &mut cx,
 7014    );
 7015
 7016    // selection of multiline block comments
 7017    assert_rewrap(
 7018        indoc! {"
 7019            «/* Lorem ipsum dolor sit amet,
 7020             * consectetur adipiscing elit. */ˇ»
 7021        "},
 7022        indoc! {"
 7023            «/*
 7024             * Lorem ipsum dolor sit amet,
 7025             * consectetur adipiscing elit.
 7026             */ˇ»
 7027        "},
 7028        rust_lang.clone(),
 7029        &mut cx,
 7030    );
 7031
 7032    // partial selection of multiline block comments
 7033    assert_rewrap(
 7034        indoc! {"
 7035            «/* Lorem ipsum dolor sit amet,ˇ»
 7036             * consectetur adipiscing elit. */
 7037            /* Lorem ipsum dolor sit amet,
 7038             «* consectetur adipiscing elit. */ˇ»
 7039        "},
 7040        indoc! {"
 7041            «/*
 7042             * Lorem ipsum dolor sit amet,ˇ»
 7043             * consectetur adipiscing elit. */
 7044            /* Lorem ipsum dolor sit amet,
 7045             «* consectetur adipiscing elit.
 7046             */ˇ»
 7047        "},
 7048        rust_lang.clone(),
 7049        &mut cx,
 7050    );
 7051
 7052    // selection w/ abutting short block comments
 7053    // TODO: should not be combined; should rewrap as 2 comments
 7054    assert_rewrap(
 7055        indoc! {"
 7056            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 7057            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 7058        "},
 7059        // desired behavior:
 7060        // indoc! {"
 7061        //     «/*
 7062        //      * Lorem ipsum dolor sit amet,
 7063        //      * consectetur adipiscing elit.
 7064        //      */
 7065        //     /*
 7066        //      * Lorem ipsum dolor sit amet,
 7067        //      * consectetur adipiscing elit.
 7068        //      */ˇ»
 7069        // "},
 7070        // actual behaviour:
 7071        indoc! {"
 7072            «/*
 7073             * Lorem ipsum dolor sit amet,
 7074             * consectetur adipiscing elit. Lorem
 7075             * ipsum dolor sit amet, consectetur
 7076             * adipiscing elit.
 7077             */ˇ»
 7078        "},
 7079        rust_lang.clone(),
 7080        &mut cx,
 7081    );
 7082
 7083    // TODO: same as above, but with delimiters on separate line
 7084    // assert_rewrap(
 7085    //     indoc! {"
 7086    //         «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 7087    //          */
 7088    //         /*
 7089    //          * Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 7090    //     "},
 7091    //     // desired:
 7092    //     // indoc! {"
 7093    //     //     «/*
 7094    //     //      * Lorem ipsum dolor sit amet,
 7095    //     //      * consectetur adipiscing elit.
 7096    //     //      */
 7097    //     //     /*
 7098    //     //      * Lorem ipsum dolor sit amet,
 7099    //     //      * consectetur adipiscing elit.
 7100    //     //      */ˇ»
 7101    //     // "},
 7102    //     // actual: (but with trailing w/s on the empty lines)
 7103    //     indoc! {"
 7104    //         «/*
 7105    //          * Lorem ipsum dolor sit amet,
 7106    //          * consectetur adipiscing elit.
 7107    //          *
 7108    //          */
 7109    //         /*
 7110    //          *
 7111    //          * Lorem ipsum dolor sit amet,
 7112    //          * consectetur adipiscing elit.
 7113    //          */ˇ»
 7114    //     "},
 7115    //     rust_lang.clone(),
 7116    //     &mut cx,
 7117    // );
 7118
 7119    // TODO these are unhandled edge cases; not correct, just documenting known issues
 7120    assert_rewrap(
 7121        indoc! {"
 7122            /*
 7123             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 7124             */
 7125            /*
 7126             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 7127            /*ˇ Lorem ipsum dolor sit amet */ /* consectetur adipiscing elit. */
 7128        "},
 7129        // desired:
 7130        // indoc! {"
 7131        //     /*
 7132        //      *ˇ Lorem ipsum dolor sit amet,
 7133        //      * consectetur adipiscing elit.
 7134        //      */
 7135        //     /*
 7136        //      *ˇ Lorem ipsum dolor sit amet,
 7137        //      * consectetur adipiscing elit.
 7138        //      */
 7139        //     /*
 7140        //      *ˇ Lorem ipsum dolor sit amet
 7141        //      */ /* consectetur adipiscing elit. */
 7142        // "},
 7143        // actual:
 7144        indoc! {"
 7145            /*
 7146             //ˇ Lorem ipsum dolor sit amet,
 7147             // consectetur adipiscing elit.
 7148             */
 7149            /*
 7150             * //ˇ Lorem ipsum dolor sit amet,
 7151             * consectetur adipiscing elit.
 7152             */
 7153            /*
 7154             *ˇ Lorem ipsum dolor sit amet */ /*
 7155             * consectetur adipiscing elit.
 7156             */
 7157        "},
 7158        rust_lang,
 7159        &mut cx,
 7160    );
 7161
 7162    #[track_caller]
 7163    fn assert_rewrap(
 7164        unwrapped_text: &str,
 7165        wrapped_text: &str,
 7166        language: Arc<Language>,
 7167        cx: &mut EditorTestContext,
 7168    ) {
 7169        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 7170        cx.set_state(unwrapped_text);
 7171        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 7172        cx.assert_editor_state(wrapped_text);
 7173    }
 7174}
 7175
 7176#[gpui::test]
 7177async fn test_hard_wrap(cx: &mut TestAppContext) {
 7178    init_test(cx, |_| {});
 7179    let mut cx = EditorTestContext::new(cx).await;
 7180
 7181    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 7182    cx.update_editor(|editor, _, cx| {
 7183        editor.set_hard_wrap(Some(14), cx);
 7184    });
 7185
 7186    cx.set_state(indoc!(
 7187        "
 7188        one two three ˇ
 7189        "
 7190    ));
 7191    cx.simulate_input("four");
 7192    cx.run_until_parked();
 7193
 7194    cx.assert_editor_state(indoc!(
 7195        "
 7196        one two three
 7197        fourˇ
 7198        "
 7199    ));
 7200
 7201    cx.update_editor(|editor, window, cx| {
 7202        editor.newline(&Default::default(), window, cx);
 7203    });
 7204    cx.run_until_parked();
 7205    cx.assert_editor_state(indoc!(
 7206        "
 7207        one two three
 7208        four
 7209        ˇ
 7210        "
 7211    ));
 7212
 7213    cx.simulate_input("five");
 7214    cx.run_until_parked();
 7215    cx.assert_editor_state(indoc!(
 7216        "
 7217        one two three
 7218        four
 7219        fiveˇ
 7220        "
 7221    ));
 7222
 7223    cx.update_editor(|editor, window, cx| {
 7224        editor.newline(&Default::default(), window, cx);
 7225    });
 7226    cx.run_until_parked();
 7227    cx.simulate_input("# ");
 7228    cx.run_until_parked();
 7229    cx.assert_editor_state(indoc!(
 7230        "
 7231        one two three
 7232        four
 7233        five
 7234        # ˇ
 7235        "
 7236    ));
 7237
 7238    cx.update_editor(|editor, window, cx| {
 7239        editor.newline(&Default::default(), window, cx);
 7240    });
 7241    cx.run_until_parked();
 7242    cx.assert_editor_state(indoc!(
 7243        "
 7244        one two three
 7245        four
 7246        five
 7247        #\x20
 7248 7249        "
 7250    ));
 7251
 7252    cx.simulate_input(" 6");
 7253    cx.run_until_parked();
 7254    cx.assert_editor_state(indoc!(
 7255        "
 7256        one two three
 7257        four
 7258        five
 7259        #
 7260        # 6ˇ
 7261        "
 7262    ));
 7263}
 7264
 7265#[gpui::test]
 7266async fn test_cut_line_ends(cx: &mut TestAppContext) {
 7267    init_test(cx, |_| {});
 7268
 7269    let mut cx = EditorTestContext::new(cx).await;
 7270
 7271    cx.set_state(indoc! {"The quick brownˇ"});
 7272    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 7273    cx.assert_editor_state(indoc! {"The quick brownˇ"});
 7274
 7275    cx.set_state(indoc! {"The emacs foxˇ"});
 7276    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 7277    cx.assert_editor_state(indoc! {"The emacs foxˇ"});
 7278
 7279    cx.set_state(indoc! {"
 7280        The quick« brownˇ»
 7281        fox jumps overˇ
 7282        the lazy dog"});
 7283    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7284    cx.assert_editor_state(indoc! {"
 7285        The quickˇ
 7286        ˇthe lazy dog"});
 7287
 7288    cx.set_state(indoc! {"
 7289        The quick« brownˇ»
 7290        fox jumps overˇ
 7291        the lazy dog"});
 7292    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 7293    cx.assert_editor_state(indoc! {"
 7294        The quickˇ
 7295        fox jumps overˇthe lazy dog"});
 7296
 7297    cx.set_state(indoc! {"
 7298        The quick« brownˇ»
 7299        fox jumps overˇ
 7300        the lazy dog"});
 7301    cx.update_editor(|e, window, cx| {
 7302        e.cut_to_end_of_line(
 7303            &CutToEndOfLine {
 7304                stop_at_newlines: true,
 7305            },
 7306            window,
 7307            cx,
 7308        )
 7309    });
 7310    cx.assert_editor_state(indoc! {"
 7311        The quickˇ
 7312        fox jumps overˇ
 7313        the lazy dog"});
 7314
 7315    cx.set_state(indoc! {"
 7316        The quick« brownˇ»
 7317        fox jumps overˇ
 7318        the lazy dog"});
 7319    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 7320    cx.assert_editor_state(indoc! {"
 7321        The quickˇ
 7322        fox jumps overˇthe lazy dog"});
 7323}
 7324
 7325#[gpui::test]
 7326async fn test_clipboard(cx: &mut TestAppContext) {
 7327    init_test(cx, |_| {});
 7328
 7329    let mut cx = EditorTestContext::new(cx).await;
 7330
 7331    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 7332    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7333    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 7334
 7335    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 7336    cx.set_state("two ˇfour ˇsix ˇ");
 7337    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7338    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 7339
 7340    // Paste again but with only two cursors. Since the number of cursors doesn't
 7341    // match the number of slices in the clipboard, the entire clipboard text
 7342    // is pasted at each cursor.
 7343    cx.set_state("ˇtwo one✅ four three six five ˇ");
 7344    cx.update_editor(|e, window, cx| {
 7345        e.handle_input("( ", window, cx);
 7346        e.paste(&Paste, window, cx);
 7347        e.handle_input(") ", window, cx);
 7348    });
 7349    cx.assert_editor_state(
 7350        &([
 7351            "( one✅ ",
 7352            "three ",
 7353            "five ) ˇtwo one✅ four three six five ( one✅ ",
 7354            "three ",
 7355            "five ) ˇ",
 7356        ]
 7357        .join("\n")),
 7358    );
 7359
 7360    // Cut with three selections, one of which is full-line.
 7361    cx.set_state(indoc! {"
 7362        1«2ˇ»3
 7363        4ˇ567
 7364        «8ˇ»9"});
 7365    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7366    cx.assert_editor_state(indoc! {"
 7367        1ˇ3
 7368        ˇ9"});
 7369
 7370    // Paste with three selections, noticing how the copied selection that was full-line
 7371    // gets inserted before the second cursor.
 7372    cx.set_state(indoc! {"
 7373        1ˇ3
 7374 7375        «oˇ»ne"});
 7376    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7377    cx.assert_editor_state(indoc! {"
 7378        12ˇ3
 7379        4567
 7380 7381        8ˇne"});
 7382
 7383    // Copy with a single cursor only, which writes the whole line into the clipboard.
 7384    cx.set_state(indoc! {"
 7385        The quick brown
 7386        fox juˇmps over
 7387        the lazy dog"});
 7388    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7389    assert_eq!(
 7390        cx.read_from_clipboard()
 7391            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7392        Some("fox jumps over\n".to_string())
 7393    );
 7394
 7395    // Paste with three selections, noticing how the copied full-line selection is inserted
 7396    // before the empty selections but replaces the selection that is non-empty.
 7397    cx.set_state(indoc! {"
 7398        Tˇhe quick brown
 7399        «foˇ»x jumps over
 7400        tˇhe lazy dog"});
 7401    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7402    cx.assert_editor_state(indoc! {"
 7403        fox jumps over
 7404        Tˇhe quick brown
 7405        fox jumps over
 7406        ˇx jumps over
 7407        fox jumps over
 7408        tˇhe lazy dog"});
 7409}
 7410
 7411#[gpui::test]
 7412async fn test_copy_trim(cx: &mut TestAppContext) {
 7413    init_test(cx, |_| {});
 7414
 7415    let mut cx = EditorTestContext::new(cx).await;
 7416    cx.set_state(
 7417        r#"            «for selection in selections.iter() {
 7418            let mut start = selection.start;
 7419            let mut end = selection.end;
 7420            let is_entire_line = selection.is_empty();
 7421            if is_entire_line {
 7422                start = Point::new(start.row, 0);ˇ»
 7423                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7424            }
 7425        "#,
 7426    );
 7427    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7428    assert_eq!(
 7429        cx.read_from_clipboard()
 7430            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7431        Some(
 7432            "for selection in selections.iter() {
 7433            let mut start = selection.start;
 7434            let mut end = selection.end;
 7435            let is_entire_line = selection.is_empty();
 7436            if is_entire_line {
 7437                start = Point::new(start.row, 0);"
 7438                .to_string()
 7439        ),
 7440        "Regular copying preserves all indentation selected",
 7441    );
 7442    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7443    assert_eq!(
 7444        cx.read_from_clipboard()
 7445            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7446        Some(
 7447            "for selection in selections.iter() {
 7448let mut start = selection.start;
 7449let mut end = selection.end;
 7450let is_entire_line = selection.is_empty();
 7451if is_entire_line {
 7452    start = Point::new(start.row, 0);"
 7453                .to_string()
 7454        ),
 7455        "Copying with stripping should strip all leading whitespaces"
 7456    );
 7457
 7458    cx.set_state(
 7459        r#"       «     for selection in selections.iter() {
 7460            let mut start = selection.start;
 7461            let mut end = selection.end;
 7462            let is_entire_line = selection.is_empty();
 7463            if is_entire_line {
 7464                start = Point::new(start.row, 0);ˇ»
 7465                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7466            }
 7467        "#,
 7468    );
 7469    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7470    assert_eq!(
 7471        cx.read_from_clipboard()
 7472            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7473        Some(
 7474            "     for selection in selections.iter() {
 7475            let mut start = selection.start;
 7476            let mut end = selection.end;
 7477            let is_entire_line = selection.is_empty();
 7478            if is_entire_line {
 7479                start = Point::new(start.row, 0);"
 7480                .to_string()
 7481        ),
 7482        "Regular copying preserves all indentation selected",
 7483    );
 7484    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7485    assert_eq!(
 7486        cx.read_from_clipboard()
 7487            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7488        Some(
 7489            "for selection in selections.iter() {
 7490let mut start = selection.start;
 7491let mut end = selection.end;
 7492let is_entire_line = selection.is_empty();
 7493if is_entire_line {
 7494    start = Point::new(start.row, 0);"
 7495                .to_string()
 7496        ),
 7497        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 7498    );
 7499
 7500    cx.set_state(
 7501        r#"       «ˇ     for selection in selections.iter() {
 7502            let mut start = selection.start;
 7503            let mut end = selection.end;
 7504            let is_entire_line = selection.is_empty();
 7505            if is_entire_line {
 7506                start = Point::new(start.row, 0);»
 7507                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7508            }
 7509        "#,
 7510    );
 7511    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7512    assert_eq!(
 7513        cx.read_from_clipboard()
 7514            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7515        Some(
 7516            "     for selection in selections.iter() {
 7517            let mut start = selection.start;
 7518            let mut end = selection.end;
 7519            let is_entire_line = selection.is_empty();
 7520            if is_entire_line {
 7521                start = Point::new(start.row, 0);"
 7522                .to_string()
 7523        ),
 7524        "Regular copying for reverse selection works the same",
 7525    );
 7526    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7527    assert_eq!(
 7528        cx.read_from_clipboard()
 7529            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7530        Some(
 7531            "for selection in selections.iter() {
 7532let mut start = selection.start;
 7533let mut end = selection.end;
 7534let is_entire_line = selection.is_empty();
 7535if is_entire_line {
 7536    start = Point::new(start.row, 0);"
 7537                .to_string()
 7538        ),
 7539        "Copying with stripping for reverse selection works the same"
 7540    );
 7541
 7542    cx.set_state(
 7543        r#"            for selection «in selections.iter() {
 7544            let mut start = selection.start;
 7545            let mut end = selection.end;
 7546            let is_entire_line = selection.is_empty();
 7547            if is_entire_line {
 7548                start = Point::new(start.row, 0);ˇ»
 7549                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7550            }
 7551        "#,
 7552    );
 7553    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7554    assert_eq!(
 7555        cx.read_from_clipboard()
 7556            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7557        Some(
 7558            "in selections.iter() {
 7559            let mut start = selection.start;
 7560            let mut end = selection.end;
 7561            let is_entire_line = selection.is_empty();
 7562            if is_entire_line {
 7563                start = Point::new(start.row, 0);"
 7564                .to_string()
 7565        ),
 7566        "When selecting past the indent, the copying works as usual",
 7567    );
 7568    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7569    assert_eq!(
 7570        cx.read_from_clipboard()
 7571            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7572        Some(
 7573            "in selections.iter() {
 7574            let mut start = selection.start;
 7575            let mut end = selection.end;
 7576            let is_entire_line = selection.is_empty();
 7577            if is_entire_line {
 7578                start = Point::new(start.row, 0);"
 7579                .to_string()
 7580        ),
 7581        "When selecting past the indent, nothing is trimmed"
 7582    );
 7583
 7584    cx.set_state(
 7585        r#"            «for selection in selections.iter() {
 7586            let mut start = selection.start;
 7587
 7588            let mut end = selection.end;
 7589            let is_entire_line = selection.is_empty();
 7590            if is_entire_line {
 7591                start = Point::new(start.row, 0);
 7592ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7593            }
 7594        "#,
 7595    );
 7596    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7597    assert_eq!(
 7598        cx.read_from_clipboard()
 7599            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7600        Some(
 7601            "for selection in selections.iter() {
 7602let mut start = selection.start;
 7603
 7604let mut end = selection.end;
 7605let is_entire_line = selection.is_empty();
 7606if is_entire_line {
 7607    start = Point::new(start.row, 0);
 7608"
 7609            .to_string()
 7610        ),
 7611        "Copying with stripping should ignore empty lines"
 7612    );
 7613}
 7614
 7615#[gpui::test]
 7616async fn test_copy_trim_line_mode(cx: &mut TestAppContext) {
 7617    init_test(cx, |_| {});
 7618
 7619    let mut cx = EditorTestContext::new(cx).await;
 7620
 7621    cx.set_state(indoc! {"
 7622        «    a
 7623            bˇ»
 7624    "});
 7625    cx.update_editor(|editor, _window, _cx| editor.selections.set_line_mode(true));
 7626    cx.update_editor(|editor, window, cx| editor.copy_and_trim(&CopyAndTrim, window, cx));
 7627
 7628    assert_eq!(
 7629        cx.read_from_clipboard().and_then(|item| item.text()),
 7630        Some("a\nb\n".to_string())
 7631    );
 7632}
 7633
 7634#[gpui::test]
 7635async fn test_clipboard_line_numbers_from_multibuffer(cx: &mut TestAppContext) {
 7636    init_test(cx, |_| {});
 7637
 7638    let fs = FakeFs::new(cx.executor());
 7639    fs.insert_file(
 7640        path!("/file.txt"),
 7641        "first line\nsecond line\nthird line\nfourth line\nfifth line\n".into(),
 7642    )
 7643    .await;
 7644
 7645    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
 7646
 7647    let buffer = project
 7648        .update(cx, |project, cx| {
 7649            project.open_local_buffer(path!("/file.txt"), cx)
 7650        })
 7651        .await
 7652        .unwrap();
 7653
 7654    let multibuffer = cx.new(|cx| {
 7655        let mut multibuffer = MultiBuffer::new(ReadWrite);
 7656        multibuffer.push_excerpts(
 7657            buffer.clone(),
 7658            [ExcerptRange::new(Point::new(2, 0)..Point::new(5, 0))],
 7659            cx,
 7660        );
 7661        multibuffer
 7662    });
 7663
 7664    let (editor, cx) = cx.add_window_view(|window, cx| {
 7665        build_editor_with_project(project.clone(), multibuffer, window, cx)
 7666    });
 7667
 7668    editor.update_in(cx, |editor, window, cx| {
 7669        assert_eq!(editor.text(cx), "third line\nfourth line\nfifth line\n");
 7670
 7671        editor.select_all(&SelectAll, window, cx);
 7672        editor.copy(&Copy, window, cx);
 7673    });
 7674
 7675    let clipboard_selections: Option<Vec<ClipboardSelection>> = cx
 7676        .read_from_clipboard()
 7677        .and_then(|item| item.entries().first().cloned())
 7678        .and_then(|entry| match entry {
 7679            gpui::ClipboardEntry::String(text) => text.metadata_json(),
 7680            _ => None,
 7681        });
 7682
 7683    let selections = clipboard_selections.expect("should have clipboard selections");
 7684    assert_eq!(selections.len(), 1);
 7685    let selection = &selections[0];
 7686    assert_eq!(
 7687        selection.line_range,
 7688        Some(2..=5),
 7689        "line range should be from original file (rows 2-5), not multibuffer rows (0-2)"
 7690    );
 7691}
 7692
 7693#[gpui::test]
 7694async fn test_paste_multiline(cx: &mut TestAppContext) {
 7695    init_test(cx, |_| {});
 7696
 7697    let mut cx = EditorTestContext::new(cx).await;
 7698    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7699
 7700    // Cut an indented block, without the leading whitespace.
 7701    cx.set_state(indoc! {"
 7702        const a: B = (
 7703            c(),
 7704            «d(
 7705                e,
 7706                f
 7707            )ˇ»
 7708        );
 7709    "});
 7710    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7711    cx.assert_editor_state(indoc! {"
 7712        const a: B = (
 7713            c(),
 7714            ˇ
 7715        );
 7716    "});
 7717
 7718    // Paste it at the same position.
 7719    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7720    cx.assert_editor_state(indoc! {"
 7721        const a: B = (
 7722            c(),
 7723            d(
 7724                e,
 7725                f
 7726 7727        );
 7728    "});
 7729
 7730    // Paste it at a line with a lower indent level.
 7731    cx.set_state(indoc! {"
 7732        ˇ
 7733        const a: B = (
 7734            c(),
 7735        );
 7736    "});
 7737    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7738    cx.assert_editor_state(indoc! {"
 7739        d(
 7740            e,
 7741            f
 7742 7743        const a: B = (
 7744            c(),
 7745        );
 7746    "});
 7747
 7748    // Cut an indented block, with the leading whitespace.
 7749    cx.set_state(indoc! {"
 7750        const a: B = (
 7751            c(),
 7752        «    d(
 7753                e,
 7754                f
 7755            )
 7756        ˇ»);
 7757    "});
 7758    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7759    cx.assert_editor_state(indoc! {"
 7760        const a: B = (
 7761            c(),
 7762        ˇ);
 7763    "});
 7764
 7765    // Paste it at the same position.
 7766    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7767    cx.assert_editor_state(indoc! {"
 7768        const a: B = (
 7769            c(),
 7770            d(
 7771                e,
 7772                f
 7773            )
 7774        ˇ);
 7775    "});
 7776
 7777    // Paste it at a line with a higher indent level.
 7778    cx.set_state(indoc! {"
 7779        const a: B = (
 7780            c(),
 7781            d(
 7782                e,
 7783 7784            )
 7785        );
 7786    "});
 7787    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7788    cx.assert_editor_state(indoc! {"
 7789        const a: B = (
 7790            c(),
 7791            d(
 7792                e,
 7793                f    d(
 7794                    e,
 7795                    f
 7796                )
 7797        ˇ
 7798            )
 7799        );
 7800    "});
 7801
 7802    // Copy an indented block, starting mid-line
 7803    cx.set_state(indoc! {"
 7804        const a: B = (
 7805            c(),
 7806            somethin«g(
 7807                e,
 7808                f
 7809            )ˇ»
 7810        );
 7811    "});
 7812    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7813
 7814    // Paste it on a line with a lower indent level
 7815    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 7816    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7817    cx.assert_editor_state(indoc! {"
 7818        const a: B = (
 7819            c(),
 7820            something(
 7821                e,
 7822                f
 7823            )
 7824        );
 7825        g(
 7826            e,
 7827            f
 7828"});
 7829}
 7830
 7831#[gpui::test]
 7832async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 7833    init_test(cx, |_| {});
 7834
 7835    cx.write_to_clipboard(ClipboardItem::new_string(
 7836        "    d(\n        e\n    );\n".into(),
 7837    ));
 7838
 7839    let mut cx = EditorTestContext::new(cx).await;
 7840    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7841
 7842    cx.set_state(indoc! {"
 7843        fn a() {
 7844            b();
 7845            if c() {
 7846                ˇ
 7847            }
 7848        }
 7849    "});
 7850
 7851    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7852    cx.assert_editor_state(indoc! {"
 7853        fn a() {
 7854            b();
 7855            if c() {
 7856                d(
 7857                    e
 7858                );
 7859        ˇ
 7860            }
 7861        }
 7862    "});
 7863
 7864    cx.set_state(indoc! {"
 7865        fn a() {
 7866            b();
 7867            ˇ
 7868        }
 7869    "});
 7870
 7871    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7872    cx.assert_editor_state(indoc! {"
 7873        fn a() {
 7874            b();
 7875            d(
 7876                e
 7877            );
 7878        ˇ
 7879        }
 7880    "});
 7881}
 7882
 7883#[gpui::test]
 7884fn test_select_all(cx: &mut TestAppContext) {
 7885    init_test(cx, |_| {});
 7886
 7887    let editor = cx.add_window(|window, cx| {
 7888        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 7889        build_editor(buffer, window, cx)
 7890    });
 7891    _ = editor.update(cx, |editor, window, cx| {
 7892        editor.select_all(&SelectAll, window, cx);
 7893        assert_eq!(
 7894            display_ranges(editor, cx),
 7895            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 7896        );
 7897    });
 7898}
 7899
 7900#[gpui::test]
 7901fn test_select_line(cx: &mut TestAppContext) {
 7902    init_test(cx, |_| {});
 7903
 7904    let editor = cx.add_window(|window, cx| {
 7905        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 7906        build_editor(buffer, window, cx)
 7907    });
 7908    _ = editor.update(cx, |editor, window, cx| {
 7909        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7910            s.select_display_ranges([
 7911                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7912                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7913                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7914                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 7915            ])
 7916        });
 7917        editor.select_line(&SelectLine, window, cx);
 7918        // Adjacent line selections should NOT merge (only overlapping ones do)
 7919        assert_eq!(
 7920            display_ranges(editor, cx),
 7921            vec![
 7922                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7923                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(2), 0),
 7924                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 7925            ]
 7926        );
 7927    });
 7928
 7929    _ = editor.update(cx, |editor, window, cx| {
 7930        editor.select_line(&SelectLine, window, cx);
 7931        assert_eq!(
 7932            display_ranges(editor, cx),
 7933            vec![
 7934                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 7935                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 7936            ]
 7937        );
 7938    });
 7939
 7940    _ = editor.update(cx, |editor, window, cx| {
 7941        editor.select_line(&SelectLine, window, cx);
 7942        // Adjacent but not overlapping, so they stay separate
 7943        assert_eq!(
 7944            display_ranges(editor, cx),
 7945            vec![
 7946                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(4), 0),
 7947                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 7948            ]
 7949        );
 7950    });
 7951}
 7952
 7953#[gpui::test]
 7954async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 7955    init_test(cx, |_| {});
 7956    let mut cx = EditorTestContext::new(cx).await;
 7957
 7958    #[track_caller]
 7959    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 7960        cx.set_state(initial_state);
 7961        cx.update_editor(|e, window, cx| {
 7962            e.split_selection_into_lines(&Default::default(), window, cx)
 7963        });
 7964        cx.assert_editor_state(expected_state);
 7965    }
 7966
 7967    // Selection starts and ends at the middle of lines, left-to-right
 7968    test(
 7969        &mut cx,
 7970        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 7971        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7972    );
 7973    // Same thing, right-to-left
 7974    test(
 7975        &mut cx,
 7976        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 7977        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7978    );
 7979
 7980    // Whole buffer, left-to-right, last line *doesn't* end with newline
 7981    test(
 7982        &mut cx,
 7983        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 7984        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7985    );
 7986    // Same thing, right-to-left
 7987    test(
 7988        &mut cx,
 7989        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 7990        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7991    );
 7992
 7993    // Whole buffer, left-to-right, last line ends with newline
 7994    test(
 7995        &mut cx,
 7996        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 7997        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7998    );
 7999    // Same thing, right-to-left
 8000    test(
 8001        &mut cx,
 8002        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 8003        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 8004    );
 8005
 8006    // Starts at the end of a line, ends at the start of another
 8007    test(
 8008        &mut cx,
 8009        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 8010        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 8011    );
 8012}
 8013
 8014#[gpui::test]
 8015async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 8016    init_test(cx, |_| {});
 8017
 8018    let editor = cx.add_window(|window, cx| {
 8019        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 8020        build_editor(buffer, window, cx)
 8021    });
 8022
 8023    // setup
 8024    _ = editor.update(cx, |editor, window, cx| {
 8025        editor.fold_creases(
 8026            vec![
 8027                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 8028                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 8029                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 8030            ],
 8031            true,
 8032            window,
 8033            cx,
 8034        );
 8035        assert_eq!(
 8036            editor.display_text(cx),
 8037            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 8038        );
 8039    });
 8040
 8041    _ = editor.update(cx, |editor, window, cx| {
 8042        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8043            s.select_display_ranges([
 8044                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8045                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 8046                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 8047                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 8048            ])
 8049        });
 8050        editor.split_selection_into_lines(&Default::default(), window, cx);
 8051        assert_eq!(
 8052            editor.display_text(cx),
 8053            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 8054        );
 8055    });
 8056    EditorTestContext::for_editor(editor, cx)
 8057        .await
 8058        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 8059
 8060    _ = editor.update(cx, |editor, window, cx| {
 8061        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8062            s.select_display_ranges([
 8063                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 8064            ])
 8065        });
 8066        editor.split_selection_into_lines(&Default::default(), window, cx);
 8067        assert_eq!(
 8068            editor.display_text(cx),
 8069            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 8070        );
 8071        assert_eq!(
 8072            display_ranges(editor, cx),
 8073            [
 8074                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 8075                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 8076                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 8077                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 8078                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 8079                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 8080                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 8081            ]
 8082        );
 8083    });
 8084    EditorTestContext::for_editor(editor, cx)
 8085        .await
 8086        .assert_editor_state(
 8087            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 8088        );
 8089}
 8090
 8091#[gpui::test]
 8092async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 8093    init_test(cx, |_| {});
 8094
 8095    let mut cx = EditorTestContext::new(cx).await;
 8096
 8097    cx.set_state(indoc!(
 8098        r#"abc
 8099           defˇghi
 8100
 8101           jk
 8102           nlmo
 8103           "#
 8104    ));
 8105
 8106    cx.update_editor(|editor, window, cx| {
 8107        editor.add_selection_above(&Default::default(), window, cx);
 8108    });
 8109
 8110    cx.assert_editor_state(indoc!(
 8111        r#"abcˇ
 8112           defˇghi
 8113
 8114           jk
 8115           nlmo
 8116           "#
 8117    ));
 8118
 8119    cx.update_editor(|editor, window, cx| {
 8120        editor.add_selection_above(&Default::default(), window, cx);
 8121    });
 8122
 8123    cx.assert_editor_state(indoc!(
 8124        r#"abcˇ
 8125            defˇghi
 8126
 8127            jk
 8128            nlmo
 8129            "#
 8130    ));
 8131
 8132    cx.update_editor(|editor, window, cx| {
 8133        editor.add_selection_below(&Default::default(), window, cx);
 8134    });
 8135
 8136    cx.assert_editor_state(indoc!(
 8137        r#"abc
 8138           defˇghi
 8139
 8140           jk
 8141           nlmo
 8142           "#
 8143    ));
 8144
 8145    cx.update_editor(|editor, window, cx| {
 8146        editor.undo_selection(&Default::default(), window, cx);
 8147    });
 8148
 8149    cx.assert_editor_state(indoc!(
 8150        r#"abcˇ
 8151           defˇghi
 8152
 8153           jk
 8154           nlmo
 8155           "#
 8156    ));
 8157
 8158    cx.update_editor(|editor, window, cx| {
 8159        editor.redo_selection(&Default::default(), window, cx);
 8160    });
 8161
 8162    cx.assert_editor_state(indoc!(
 8163        r#"abc
 8164           defˇghi
 8165
 8166           jk
 8167           nlmo
 8168           "#
 8169    ));
 8170
 8171    cx.update_editor(|editor, window, cx| {
 8172        editor.add_selection_below(&Default::default(), window, cx);
 8173    });
 8174
 8175    cx.assert_editor_state(indoc!(
 8176        r#"abc
 8177           defˇghi
 8178           ˇ
 8179           jk
 8180           nlmo
 8181           "#
 8182    ));
 8183
 8184    cx.update_editor(|editor, window, cx| {
 8185        editor.add_selection_below(&Default::default(), window, cx);
 8186    });
 8187
 8188    cx.assert_editor_state(indoc!(
 8189        r#"abc
 8190           defˇghi
 8191           ˇ
 8192           jkˇ
 8193           nlmo
 8194           "#
 8195    ));
 8196
 8197    cx.update_editor(|editor, window, cx| {
 8198        editor.add_selection_below(&Default::default(), window, cx);
 8199    });
 8200
 8201    cx.assert_editor_state(indoc!(
 8202        r#"abc
 8203           defˇghi
 8204           ˇ
 8205           jkˇ
 8206           nlmˇo
 8207           "#
 8208    ));
 8209
 8210    cx.update_editor(|editor, window, cx| {
 8211        editor.add_selection_below(&Default::default(), window, cx);
 8212    });
 8213
 8214    cx.assert_editor_state(indoc!(
 8215        r#"abc
 8216           defˇghi
 8217           ˇ
 8218           jkˇ
 8219           nlmˇo
 8220           ˇ"#
 8221    ));
 8222
 8223    // change selections
 8224    cx.set_state(indoc!(
 8225        r#"abc
 8226           def«ˇg»hi
 8227
 8228           jk
 8229           nlmo
 8230           "#
 8231    ));
 8232
 8233    cx.update_editor(|editor, window, cx| {
 8234        editor.add_selection_below(&Default::default(), window, cx);
 8235    });
 8236
 8237    cx.assert_editor_state(indoc!(
 8238        r#"abc
 8239           def«ˇg»hi
 8240
 8241           jk
 8242           nlm«ˇo»
 8243           "#
 8244    ));
 8245
 8246    cx.update_editor(|editor, window, cx| {
 8247        editor.add_selection_below(&Default::default(), window, cx);
 8248    });
 8249
 8250    cx.assert_editor_state(indoc!(
 8251        r#"abc
 8252           def«ˇg»hi
 8253
 8254           jk
 8255           nlm«ˇo»
 8256           "#
 8257    ));
 8258
 8259    cx.update_editor(|editor, window, cx| {
 8260        editor.add_selection_above(&Default::default(), window, cx);
 8261    });
 8262
 8263    cx.assert_editor_state(indoc!(
 8264        r#"abc
 8265           def«ˇg»hi
 8266
 8267           jk
 8268           nlmo
 8269           "#
 8270    ));
 8271
 8272    cx.update_editor(|editor, window, cx| {
 8273        editor.add_selection_above(&Default::default(), window, cx);
 8274    });
 8275
 8276    cx.assert_editor_state(indoc!(
 8277        r#"abc
 8278           def«ˇg»hi
 8279
 8280           jk
 8281           nlmo
 8282           "#
 8283    ));
 8284
 8285    // Change selections again
 8286    cx.set_state(indoc!(
 8287        r#"a«bc
 8288           defgˇ»hi
 8289
 8290           jk
 8291           nlmo
 8292           "#
 8293    ));
 8294
 8295    cx.update_editor(|editor, window, cx| {
 8296        editor.add_selection_below(&Default::default(), window, cx);
 8297    });
 8298
 8299    cx.assert_editor_state(indoc!(
 8300        r#"a«bcˇ»
 8301           d«efgˇ»hi
 8302
 8303           j«kˇ»
 8304           nlmo
 8305           "#
 8306    ));
 8307
 8308    cx.update_editor(|editor, window, cx| {
 8309        editor.add_selection_below(&Default::default(), window, cx);
 8310    });
 8311    cx.assert_editor_state(indoc!(
 8312        r#"a«bcˇ»
 8313           d«efgˇ»hi
 8314
 8315           j«kˇ»
 8316           n«lmoˇ»
 8317           "#
 8318    ));
 8319    cx.update_editor(|editor, window, cx| {
 8320        editor.add_selection_above(&Default::default(), window, cx);
 8321    });
 8322
 8323    cx.assert_editor_state(indoc!(
 8324        r#"a«bcˇ»
 8325           d«efgˇ»hi
 8326
 8327           j«kˇ»
 8328           nlmo
 8329           "#
 8330    ));
 8331
 8332    // Change selections again
 8333    cx.set_state(indoc!(
 8334        r#"abc
 8335           d«ˇefghi
 8336
 8337           jk
 8338           nlm»o
 8339           "#
 8340    ));
 8341
 8342    cx.update_editor(|editor, window, cx| {
 8343        editor.add_selection_above(&Default::default(), window, cx);
 8344    });
 8345
 8346    cx.assert_editor_state(indoc!(
 8347        r#"a«ˇbc»
 8348           d«ˇef»ghi
 8349
 8350           j«ˇk»
 8351           n«ˇlm»o
 8352           "#
 8353    ));
 8354
 8355    cx.update_editor(|editor, window, cx| {
 8356        editor.add_selection_below(&Default::default(), window, cx);
 8357    });
 8358
 8359    cx.assert_editor_state(indoc!(
 8360        r#"abc
 8361           d«ˇef»ghi
 8362
 8363           j«ˇk»
 8364           n«ˇlm»o
 8365           "#
 8366    ));
 8367
 8368    // Assert that the oldest selection's goal column is used when adding more
 8369    // selections, not the most recently added selection's actual column.
 8370    cx.set_state(indoc! {"
 8371        foo bar bazˇ
 8372        foo
 8373        foo bar
 8374    "});
 8375
 8376    cx.update_editor(|editor, window, cx| {
 8377        editor.add_selection_below(
 8378            &AddSelectionBelow {
 8379                skip_soft_wrap: true,
 8380            },
 8381            window,
 8382            cx,
 8383        );
 8384    });
 8385
 8386    cx.assert_editor_state(indoc! {"
 8387        foo bar bazˇ
 8388        fooˇ
 8389        foo bar
 8390    "});
 8391
 8392    cx.update_editor(|editor, window, cx| {
 8393        editor.add_selection_below(
 8394            &AddSelectionBelow {
 8395                skip_soft_wrap: true,
 8396            },
 8397            window,
 8398            cx,
 8399        );
 8400    });
 8401
 8402    cx.assert_editor_state(indoc! {"
 8403        foo bar bazˇ
 8404        fooˇ
 8405        foo barˇ
 8406    "});
 8407
 8408    cx.set_state(indoc! {"
 8409        foo bar baz
 8410        foo
 8411        foo barˇ
 8412    "});
 8413
 8414    cx.update_editor(|editor, window, cx| {
 8415        editor.add_selection_above(
 8416            &AddSelectionAbove {
 8417                skip_soft_wrap: true,
 8418            },
 8419            window,
 8420            cx,
 8421        );
 8422    });
 8423
 8424    cx.assert_editor_state(indoc! {"
 8425        foo bar baz
 8426        fooˇ
 8427        foo barˇ
 8428    "});
 8429
 8430    cx.update_editor(|editor, window, cx| {
 8431        editor.add_selection_above(
 8432            &AddSelectionAbove {
 8433                skip_soft_wrap: true,
 8434            },
 8435            window,
 8436            cx,
 8437        );
 8438    });
 8439
 8440    cx.assert_editor_state(indoc! {"
 8441        foo barˇ baz
 8442        fooˇ
 8443        foo barˇ
 8444    "});
 8445}
 8446
 8447#[gpui::test]
 8448async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 8449    init_test(cx, |_| {});
 8450    let mut cx = EditorTestContext::new(cx).await;
 8451
 8452    cx.set_state(indoc!(
 8453        r#"line onˇe
 8454           liˇne two
 8455           line three
 8456           line four"#
 8457    ));
 8458
 8459    cx.update_editor(|editor, window, cx| {
 8460        editor.add_selection_below(&Default::default(), window, cx);
 8461    });
 8462
 8463    // test multiple cursors expand in the same direction
 8464    cx.assert_editor_state(indoc!(
 8465        r#"line onˇe
 8466           liˇne twˇo
 8467           liˇne three
 8468           line four"#
 8469    ));
 8470
 8471    cx.update_editor(|editor, window, cx| {
 8472        editor.add_selection_below(&Default::default(), window, cx);
 8473    });
 8474
 8475    cx.update_editor(|editor, window, cx| {
 8476        editor.add_selection_below(&Default::default(), window, cx);
 8477    });
 8478
 8479    // test multiple cursors expand below overflow
 8480    cx.assert_editor_state(indoc!(
 8481        r#"line onˇe
 8482           liˇne twˇo
 8483           liˇne thˇree
 8484           liˇne foˇur"#
 8485    ));
 8486
 8487    cx.update_editor(|editor, window, cx| {
 8488        editor.add_selection_above(&Default::default(), window, cx);
 8489    });
 8490
 8491    // test multiple cursors retrieves back correctly
 8492    cx.assert_editor_state(indoc!(
 8493        r#"line onˇe
 8494           liˇne twˇo
 8495           liˇne thˇree
 8496           line four"#
 8497    ));
 8498
 8499    cx.update_editor(|editor, window, cx| {
 8500        editor.add_selection_above(&Default::default(), window, cx);
 8501    });
 8502
 8503    cx.update_editor(|editor, window, cx| {
 8504        editor.add_selection_above(&Default::default(), window, cx);
 8505    });
 8506
 8507    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 8508    cx.assert_editor_state(indoc!(
 8509        r#"liˇne onˇe
 8510           liˇne two
 8511           line three
 8512           line four"#
 8513    ));
 8514
 8515    cx.update_editor(|editor, window, cx| {
 8516        editor.undo_selection(&Default::default(), window, cx);
 8517    });
 8518
 8519    // test undo
 8520    cx.assert_editor_state(indoc!(
 8521        r#"line onˇe
 8522           liˇne twˇo
 8523           line three
 8524           line four"#
 8525    ));
 8526
 8527    cx.update_editor(|editor, window, cx| {
 8528        editor.redo_selection(&Default::default(), window, cx);
 8529    });
 8530
 8531    // test redo
 8532    cx.assert_editor_state(indoc!(
 8533        r#"liˇne onˇe
 8534           liˇne two
 8535           line three
 8536           line four"#
 8537    ));
 8538
 8539    cx.set_state(indoc!(
 8540        r#"abcd
 8541           ef«ghˇ»
 8542           ijkl
 8543           «mˇ»nop"#
 8544    ));
 8545
 8546    cx.update_editor(|editor, window, cx| {
 8547        editor.add_selection_above(&Default::default(), window, cx);
 8548    });
 8549
 8550    // test multiple selections expand in the same direction
 8551    cx.assert_editor_state(indoc!(
 8552        r#"ab«cdˇ»
 8553           ef«ghˇ»
 8554           «iˇ»jkl
 8555           «mˇ»nop"#
 8556    ));
 8557
 8558    cx.update_editor(|editor, window, cx| {
 8559        editor.add_selection_above(&Default::default(), window, cx);
 8560    });
 8561
 8562    // test multiple selection upward overflow
 8563    cx.assert_editor_state(indoc!(
 8564        r#"ab«cdˇ»
 8565           «eˇ»f«ghˇ»
 8566           «iˇ»jkl
 8567           «mˇ»nop"#
 8568    ));
 8569
 8570    cx.update_editor(|editor, window, cx| {
 8571        editor.add_selection_below(&Default::default(), window, cx);
 8572    });
 8573
 8574    // test multiple selection retrieves back correctly
 8575    cx.assert_editor_state(indoc!(
 8576        r#"abcd
 8577           ef«ghˇ»
 8578           «iˇ»jkl
 8579           «mˇ»nop"#
 8580    ));
 8581
 8582    cx.update_editor(|editor, window, cx| {
 8583        editor.add_selection_below(&Default::default(), window, cx);
 8584    });
 8585
 8586    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 8587    cx.assert_editor_state(indoc!(
 8588        r#"abcd
 8589           ef«ghˇ»
 8590           ij«klˇ»
 8591           «mˇ»nop"#
 8592    ));
 8593
 8594    cx.update_editor(|editor, window, cx| {
 8595        editor.undo_selection(&Default::default(), window, cx);
 8596    });
 8597
 8598    // test undo
 8599    cx.assert_editor_state(indoc!(
 8600        r#"abcd
 8601           ef«ghˇ»
 8602           «iˇ»jkl
 8603           «mˇ»nop"#
 8604    ));
 8605
 8606    cx.update_editor(|editor, window, cx| {
 8607        editor.redo_selection(&Default::default(), window, cx);
 8608    });
 8609
 8610    // test redo
 8611    cx.assert_editor_state(indoc!(
 8612        r#"abcd
 8613           ef«ghˇ»
 8614           ij«klˇ»
 8615           «mˇ»nop"#
 8616    ));
 8617}
 8618
 8619#[gpui::test]
 8620async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 8621    init_test(cx, |_| {});
 8622    let mut cx = EditorTestContext::new(cx).await;
 8623
 8624    cx.set_state(indoc!(
 8625        r#"line onˇe
 8626           liˇne two
 8627           line three
 8628           line four"#
 8629    ));
 8630
 8631    cx.update_editor(|editor, window, cx| {
 8632        editor.add_selection_below(&Default::default(), window, cx);
 8633        editor.add_selection_below(&Default::default(), window, cx);
 8634        editor.add_selection_below(&Default::default(), window, cx);
 8635    });
 8636
 8637    // initial state with two multi cursor groups
 8638    cx.assert_editor_state(indoc!(
 8639        r#"line onˇe
 8640           liˇne twˇo
 8641           liˇne thˇree
 8642           liˇne foˇur"#
 8643    ));
 8644
 8645    // add single cursor in middle - simulate opt click
 8646    cx.update_editor(|editor, window, cx| {
 8647        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 8648        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 8649        editor.end_selection(window, cx);
 8650    });
 8651
 8652    cx.assert_editor_state(indoc!(
 8653        r#"line onˇe
 8654           liˇne twˇo
 8655           liˇneˇ thˇree
 8656           liˇne foˇur"#
 8657    ));
 8658
 8659    cx.update_editor(|editor, window, cx| {
 8660        editor.add_selection_above(&Default::default(), window, cx);
 8661    });
 8662
 8663    // test new added selection expands above and existing selection shrinks
 8664    cx.assert_editor_state(indoc!(
 8665        r#"line onˇe
 8666           liˇneˇ twˇo
 8667           liˇneˇ thˇree
 8668           line four"#
 8669    ));
 8670
 8671    cx.update_editor(|editor, window, cx| {
 8672        editor.add_selection_above(&Default::default(), window, cx);
 8673    });
 8674
 8675    // test new added selection expands above and existing selection shrinks
 8676    cx.assert_editor_state(indoc!(
 8677        r#"lineˇ onˇe
 8678           liˇneˇ twˇo
 8679           lineˇ three
 8680           line four"#
 8681    ));
 8682
 8683    // intial state with two selection groups
 8684    cx.set_state(indoc!(
 8685        r#"abcd
 8686           ef«ghˇ»
 8687           ijkl
 8688           «mˇ»nop"#
 8689    ));
 8690
 8691    cx.update_editor(|editor, window, cx| {
 8692        editor.add_selection_above(&Default::default(), window, cx);
 8693        editor.add_selection_above(&Default::default(), window, cx);
 8694    });
 8695
 8696    cx.assert_editor_state(indoc!(
 8697        r#"ab«cdˇ»
 8698           «eˇ»f«ghˇ»
 8699           «iˇ»jkl
 8700           «mˇ»nop"#
 8701    ));
 8702
 8703    // add single selection in middle - simulate opt drag
 8704    cx.update_editor(|editor, window, cx| {
 8705        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 8706        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 8707        editor.update_selection(
 8708            DisplayPoint::new(DisplayRow(2), 4),
 8709            0,
 8710            gpui::Point::<f32>::default(),
 8711            window,
 8712            cx,
 8713        );
 8714        editor.end_selection(window, cx);
 8715    });
 8716
 8717    cx.assert_editor_state(indoc!(
 8718        r#"ab«cdˇ»
 8719           «eˇ»f«ghˇ»
 8720           «iˇ»jk«lˇ»
 8721           «mˇ»nop"#
 8722    ));
 8723
 8724    cx.update_editor(|editor, window, cx| {
 8725        editor.add_selection_below(&Default::default(), window, cx);
 8726    });
 8727
 8728    // test new added selection expands below, others shrinks from above
 8729    cx.assert_editor_state(indoc!(
 8730        r#"abcd
 8731           ef«ghˇ»
 8732           «iˇ»jk«lˇ»
 8733           «mˇ»no«pˇ»"#
 8734    ));
 8735}
 8736
 8737#[gpui::test]
 8738async fn test_select_next(cx: &mut TestAppContext) {
 8739    init_test(cx, |_| {});
 8740    let mut cx = EditorTestContext::new(cx).await;
 8741
 8742    // Enable case sensitive search.
 8743    update_test_editor_settings(&mut cx, |settings| {
 8744        let mut search_settings = SearchSettingsContent::default();
 8745        search_settings.case_sensitive = Some(true);
 8746        settings.search = Some(search_settings);
 8747    });
 8748
 8749    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8750
 8751    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8752        .unwrap();
 8753    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8754
 8755    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8756        .unwrap();
 8757    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8758
 8759    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8760    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8761
 8762    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8763    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8764
 8765    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8766        .unwrap();
 8767    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8768
 8769    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8770        .unwrap();
 8771    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8772
 8773    // Test selection direction should be preserved
 8774    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8775
 8776    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8777        .unwrap();
 8778    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 8779
 8780    // Test case sensitivity
 8781    cx.set_state("«ˇfoo»\nFOO\nFoo\nfoo");
 8782    cx.update_editor(|e, window, cx| {
 8783        e.select_next(&SelectNext::default(), window, cx).unwrap();
 8784    });
 8785    cx.assert_editor_state("«ˇfoo»\nFOO\nFoo\n«ˇfoo»");
 8786
 8787    // Disable case sensitive search.
 8788    update_test_editor_settings(&mut cx, |settings| {
 8789        let mut search_settings = SearchSettingsContent::default();
 8790        search_settings.case_sensitive = Some(false);
 8791        settings.search = Some(search_settings);
 8792    });
 8793
 8794    cx.set_state("«ˇfoo»\nFOO\nFoo");
 8795    cx.update_editor(|e, window, cx| {
 8796        e.select_next(&SelectNext::default(), window, cx).unwrap();
 8797        e.select_next(&SelectNext::default(), window, cx).unwrap();
 8798    });
 8799    cx.assert_editor_state("«ˇfoo»\n«ˇFOO»\n«ˇFoo»");
 8800}
 8801
 8802#[gpui::test]
 8803async fn test_select_all_matches(cx: &mut TestAppContext) {
 8804    init_test(cx, |_| {});
 8805    let mut cx = EditorTestContext::new(cx).await;
 8806
 8807    // Enable case sensitive search.
 8808    update_test_editor_settings(&mut cx, |settings| {
 8809        let mut search_settings = SearchSettingsContent::default();
 8810        search_settings.case_sensitive = Some(true);
 8811        settings.search = Some(search_settings);
 8812    });
 8813
 8814    // Test caret-only selections
 8815    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8816    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8817        .unwrap();
 8818    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8819
 8820    // Test left-to-right selections
 8821    cx.set_state("abc\n«abcˇ»\nabc");
 8822    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8823        .unwrap();
 8824    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 8825
 8826    // Test right-to-left selections
 8827    cx.set_state("abc\n«ˇabc»\nabc");
 8828    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8829        .unwrap();
 8830    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 8831
 8832    // Test selecting whitespace with caret selection
 8833    cx.set_state("abc\nˇ   abc\nabc");
 8834    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8835        .unwrap();
 8836    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 8837
 8838    // Test selecting whitespace with left-to-right selection
 8839    cx.set_state("abc\n«ˇ  »abc\nabc");
 8840    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8841        .unwrap();
 8842    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 8843
 8844    // Test no matches with right-to-left selection
 8845    cx.set_state("abc\n«  ˇ»abc\nabc");
 8846    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8847        .unwrap();
 8848    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 8849
 8850    // Test with a single word and clip_at_line_ends=true (#29823)
 8851    cx.set_state("aˇbc");
 8852    cx.update_editor(|e, window, cx| {
 8853        e.set_clip_at_line_ends(true, cx);
 8854        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8855        e.set_clip_at_line_ends(false, cx);
 8856    });
 8857    cx.assert_editor_state("«abcˇ»");
 8858
 8859    // Test case sensitivity
 8860    cx.set_state("fˇoo\nFOO\nFoo");
 8861    cx.update_editor(|e, window, cx| {
 8862        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8863    });
 8864    cx.assert_editor_state("«fooˇ»\nFOO\nFoo");
 8865
 8866    // Disable case sensitive search.
 8867    update_test_editor_settings(&mut cx, |settings| {
 8868        let mut search_settings = SearchSettingsContent::default();
 8869        search_settings.case_sensitive = Some(false);
 8870        settings.search = Some(search_settings);
 8871    });
 8872
 8873    cx.set_state("fˇoo\nFOO\nFoo");
 8874    cx.update_editor(|e, window, cx| {
 8875        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8876    });
 8877    cx.assert_editor_state("«fooˇ»\n«FOOˇ»\n«Fooˇ»");
 8878}
 8879
 8880#[gpui::test]
 8881async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 8882    init_test(cx, |_| {});
 8883
 8884    let mut cx = EditorTestContext::new(cx).await;
 8885
 8886    let large_body_1 = "\nd".repeat(200);
 8887    let large_body_2 = "\ne".repeat(200);
 8888
 8889    cx.set_state(&format!(
 8890        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 8891    ));
 8892    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 8893        let scroll_position = editor.scroll_position(cx);
 8894        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 8895        scroll_position
 8896    });
 8897
 8898    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8899        .unwrap();
 8900    cx.assert_editor_state(&format!(
 8901        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 8902    ));
 8903    let scroll_position_after_selection =
 8904        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 8905    assert_eq!(
 8906        initial_scroll_position, scroll_position_after_selection,
 8907        "Scroll position should not change after selecting all matches"
 8908    );
 8909}
 8910
 8911#[gpui::test]
 8912async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 8913    init_test(cx, |_| {});
 8914
 8915    let mut cx = EditorLspTestContext::new_rust(
 8916        lsp::ServerCapabilities {
 8917            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8918            ..Default::default()
 8919        },
 8920        cx,
 8921    )
 8922    .await;
 8923
 8924    cx.set_state(indoc! {"
 8925        line 1
 8926        line 2
 8927        linˇe 3
 8928        line 4
 8929        line 5
 8930    "});
 8931
 8932    // Make an edit
 8933    cx.update_editor(|editor, window, cx| {
 8934        editor.handle_input("X", window, cx);
 8935    });
 8936
 8937    // Move cursor to a different position
 8938    cx.update_editor(|editor, window, cx| {
 8939        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8940            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 8941        });
 8942    });
 8943
 8944    cx.assert_editor_state(indoc! {"
 8945        line 1
 8946        line 2
 8947        linXe 3
 8948        line 4
 8949        liˇne 5
 8950    "});
 8951
 8952    cx.lsp
 8953        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 8954            Ok(Some(vec![lsp::TextEdit::new(
 8955                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 8956                "PREFIX ".to_string(),
 8957            )]))
 8958        });
 8959
 8960    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 8961        .unwrap()
 8962        .await
 8963        .unwrap();
 8964
 8965    cx.assert_editor_state(indoc! {"
 8966        PREFIX line 1
 8967        line 2
 8968        linXe 3
 8969        line 4
 8970        liˇne 5
 8971    "});
 8972
 8973    // Undo formatting
 8974    cx.update_editor(|editor, window, cx| {
 8975        editor.undo(&Default::default(), window, cx);
 8976    });
 8977
 8978    // Verify cursor moved back to position after edit
 8979    cx.assert_editor_state(indoc! {"
 8980        line 1
 8981        line 2
 8982        linXˇe 3
 8983        line 4
 8984        line 5
 8985    "});
 8986}
 8987
 8988#[gpui::test]
 8989async fn test_undo_edit_prediction_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 8990    init_test(cx, |_| {});
 8991
 8992    let mut cx = EditorTestContext::new(cx).await;
 8993
 8994    let provider = cx.new(|_| FakeEditPredictionDelegate::default());
 8995    cx.update_editor(|editor, window, cx| {
 8996        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 8997    });
 8998
 8999    cx.set_state(indoc! {"
 9000        line 1
 9001        line 2
 9002        linˇe 3
 9003        line 4
 9004        line 5
 9005        line 6
 9006        line 7
 9007        line 8
 9008        line 9
 9009        line 10
 9010    "});
 9011
 9012    let snapshot = cx.buffer_snapshot();
 9013    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 9014
 9015    cx.update(|_, cx| {
 9016        provider.update(cx, |provider, _| {
 9017            provider.set_edit_prediction(Some(edit_prediction_types::EditPrediction::Local {
 9018                id: None,
 9019                edits: vec![(edit_position..edit_position, "X".into())],
 9020                edit_preview: None,
 9021            }))
 9022        })
 9023    });
 9024
 9025    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
 9026    cx.update_editor(|editor, window, cx| {
 9027        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 9028    });
 9029
 9030    cx.assert_editor_state(indoc! {"
 9031        line 1
 9032        line 2
 9033        lineXˇ 3
 9034        line 4
 9035        line 5
 9036        line 6
 9037        line 7
 9038        line 8
 9039        line 9
 9040        line 10
 9041    "});
 9042
 9043    cx.update_editor(|editor, window, cx| {
 9044        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9045            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 9046        });
 9047    });
 9048
 9049    cx.assert_editor_state(indoc! {"
 9050        line 1
 9051        line 2
 9052        lineX 3
 9053        line 4
 9054        line 5
 9055        line 6
 9056        line 7
 9057        line 8
 9058        line 9
 9059        liˇne 10
 9060    "});
 9061
 9062    cx.update_editor(|editor, window, cx| {
 9063        editor.undo(&Default::default(), window, cx);
 9064    });
 9065
 9066    cx.assert_editor_state(indoc! {"
 9067        line 1
 9068        line 2
 9069        lineˇ 3
 9070        line 4
 9071        line 5
 9072        line 6
 9073        line 7
 9074        line 8
 9075        line 9
 9076        line 10
 9077    "});
 9078}
 9079
 9080#[gpui::test]
 9081async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 9082    init_test(cx, |_| {});
 9083
 9084    let mut cx = EditorTestContext::new(cx).await;
 9085    cx.set_state(
 9086        r#"let foo = 2;
 9087lˇet foo = 2;
 9088let fooˇ = 2;
 9089let foo = 2;
 9090let foo = ˇ2;"#,
 9091    );
 9092
 9093    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 9094        .unwrap();
 9095    cx.assert_editor_state(
 9096        r#"let foo = 2;
 9097«letˇ» foo = 2;
 9098let «fooˇ» = 2;
 9099let foo = 2;
 9100let foo = «2ˇ»;"#,
 9101    );
 9102
 9103    // noop for multiple selections with different contents
 9104    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 9105        .unwrap();
 9106    cx.assert_editor_state(
 9107        r#"let foo = 2;
 9108«letˇ» foo = 2;
 9109let «fooˇ» = 2;
 9110let foo = 2;
 9111let foo = «2ˇ»;"#,
 9112    );
 9113
 9114    // Test last selection direction should be preserved
 9115    cx.set_state(
 9116        r#"let foo = 2;
 9117let foo = 2;
 9118let «fooˇ» = 2;
 9119let «ˇfoo» = 2;
 9120let foo = 2;"#,
 9121    );
 9122
 9123    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 9124        .unwrap();
 9125    cx.assert_editor_state(
 9126        r#"let foo = 2;
 9127let foo = 2;
 9128let «fooˇ» = 2;
 9129let «ˇfoo» = 2;
 9130let «ˇfoo» = 2;"#,
 9131    );
 9132}
 9133
 9134#[gpui::test]
 9135async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 9136    init_test(cx, |_| {});
 9137
 9138    let mut cx =
 9139        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 9140
 9141    cx.assert_editor_state(indoc! {"
 9142        ˇbbb
 9143        ccc
 9144
 9145        bbb
 9146        ccc
 9147        "});
 9148    cx.dispatch_action(SelectPrevious::default());
 9149    cx.assert_editor_state(indoc! {"
 9150                «bbbˇ»
 9151                ccc
 9152
 9153                bbb
 9154                ccc
 9155                "});
 9156    cx.dispatch_action(SelectPrevious::default());
 9157    cx.assert_editor_state(indoc! {"
 9158                «bbbˇ»
 9159                ccc
 9160
 9161                «bbbˇ»
 9162                ccc
 9163                "});
 9164}
 9165
 9166#[gpui::test]
 9167async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 9168    init_test(cx, |_| {});
 9169
 9170    let mut cx = EditorTestContext::new(cx).await;
 9171    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 9172
 9173    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9174        .unwrap();
 9175    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 9176
 9177    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9178        .unwrap();
 9179    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 9180
 9181    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 9182    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 9183
 9184    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 9185    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 9186
 9187    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9188        .unwrap();
 9189    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 9190
 9191    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9192        .unwrap();
 9193    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 9194}
 9195
 9196#[gpui::test]
 9197async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 9198    init_test(cx, |_| {});
 9199
 9200    let mut cx = EditorTestContext::new(cx).await;
 9201    cx.set_state("");
 9202
 9203    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9204        .unwrap();
 9205    cx.assert_editor_state("«aˇ»");
 9206    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9207        .unwrap();
 9208    cx.assert_editor_state("«aˇ»");
 9209}
 9210
 9211#[gpui::test]
 9212async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 9213    init_test(cx, |_| {});
 9214
 9215    let mut cx = EditorTestContext::new(cx).await;
 9216    cx.set_state(
 9217        r#"let foo = 2;
 9218lˇet foo = 2;
 9219let fooˇ = 2;
 9220let foo = 2;
 9221let foo = ˇ2;"#,
 9222    );
 9223
 9224    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9225        .unwrap();
 9226    cx.assert_editor_state(
 9227        r#"let foo = 2;
 9228«letˇ» foo = 2;
 9229let «fooˇ» = 2;
 9230let foo = 2;
 9231let foo = «2ˇ»;"#,
 9232    );
 9233
 9234    // noop for multiple selections with different contents
 9235    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9236        .unwrap();
 9237    cx.assert_editor_state(
 9238        r#"let foo = 2;
 9239«letˇ» foo = 2;
 9240let «fooˇ» = 2;
 9241let foo = 2;
 9242let foo = «2ˇ»;"#,
 9243    );
 9244}
 9245
 9246#[gpui::test]
 9247async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 9248    init_test(cx, |_| {});
 9249    let mut cx = EditorTestContext::new(cx).await;
 9250
 9251    // Enable case sensitive search.
 9252    update_test_editor_settings(&mut cx, |settings| {
 9253        let mut search_settings = SearchSettingsContent::default();
 9254        search_settings.case_sensitive = Some(true);
 9255        settings.search = Some(search_settings);
 9256    });
 9257
 9258    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 9259
 9260    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9261        .unwrap();
 9262    // selection direction is preserved
 9263    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 9264
 9265    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9266        .unwrap();
 9267    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 9268
 9269    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 9270    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 9271
 9272    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 9273    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 9274
 9275    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9276        .unwrap();
 9277    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 9278
 9279    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9280        .unwrap();
 9281    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 9282
 9283    // Test case sensitivity
 9284    cx.set_state("foo\nFOO\nFoo\n«ˇfoo»");
 9285    cx.update_editor(|e, window, cx| {
 9286        e.select_previous(&SelectPrevious::default(), window, cx)
 9287            .unwrap();
 9288        e.select_previous(&SelectPrevious::default(), window, cx)
 9289            .unwrap();
 9290    });
 9291    cx.assert_editor_state("«ˇfoo»\nFOO\nFoo\n«ˇfoo»");
 9292
 9293    // Disable case sensitive search.
 9294    update_test_editor_settings(&mut cx, |settings| {
 9295        let mut search_settings = SearchSettingsContent::default();
 9296        search_settings.case_sensitive = Some(false);
 9297        settings.search = Some(search_settings);
 9298    });
 9299
 9300    cx.set_state("foo\nFOO\n«ˇFoo»");
 9301    cx.update_editor(|e, window, cx| {
 9302        e.select_previous(&SelectPrevious::default(), window, cx)
 9303            .unwrap();
 9304        e.select_previous(&SelectPrevious::default(), window, cx)
 9305            .unwrap();
 9306    });
 9307    cx.assert_editor_state("«ˇfoo»\n«ˇFOO»\n«ˇFoo»");
 9308}
 9309
 9310#[gpui::test]
 9311async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 9312    init_test(cx, |_| {});
 9313
 9314    let language = Arc::new(Language::new(
 9315        LanguageConfig::default(),
 9316        Some(tree_sitter_rust::LANGUAGE.into()),
 9317    ));
 9318
 9319    let text = r#"
 9320        use mod1::mod2::{mod3, mod4};
 9321
 9322        fn fn_1(param1: bool, param2: &str) {
 9323            let var1 = "text";
 9324        }
 9325    "#
 9326    .unindent();
 9327
 9328    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9329    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9330    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9331
 9332    editor
 9333        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9334        .await;
 9335
 9336    editor.update_in(cx, |editor, window, cx| {
 9337        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9338            s.select_display_ranges([
 9339                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 9340                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 9341                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 9342            ]);
 9343        });
 9344        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9345    });
 9346    editor.update(cx, |editor, cx| {
 9347        assert_text_with_selections(
 9348            editor,
 9349            indoc! {r#"
 9350                use mod1::mod2::{mod3, «mod4ˇ»};
 9351
 9352                fn fn_1«ˇ(param1: bool, param2: &str)» {
 9353                    let var1 = "«ˇtext»";
 9354                }
 9355            "#},
 9356            cx,
 9357        );
 9358    });
 9359
 9360    editor.update_in(cx, |editor, window, cx| {
 9361        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9362    });
 9363    editor.update(cx, |editor, cx| {
 9364        assert_text_with_selections(
 9365            editor,
 9366            indoc! {r#"
 9367                use mod1::mod2::«{mod3, mod4}ˇ»;
 9368
 9369                «ˇfn fn_1(param1: bool, param2: &str) {
 9370                    let var1 = "text";
 9371 9372            "#},
 9373            cx,
 9374        );
 9375    });
 9376
 9377    editor.update_in(cx, |editor, window, cx| {
 9378        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9379    });
 9380    assert_eq!(
 9381        editor.update(cx, |editor, cx| editor
 9382            .selections
 9383            .display_ranges(&editor.display_snapshot(cx))),
 9384        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 9385    );
 9386
 9387    // Trying to expand the selected syntax node one more time has no effect.
 9388    editor.update_in(cx, |editor, window, cx| {
 9389        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9390    });
 9391    assert_eq!(
 9392        editor.update(cx, |editor, cx| editor
 9393            .selections
 9394            .display_ranges(&editor.display_snapshot(cx))),
 9395        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 9396    );
 9397
 9398    editor.update_in(cx, |editor, window, cx| {
 9399        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 9400    });
 9401    editor.update(cx, |editor, cx| {
 9402        assert_text_with_selections(
 9403            editor,
 9404            indoc! {r#"
 9405                use mod1::mod2::«{mod3, mod4}ˇ»;
 9406
 9407                «ˇfn fn_1(param1: bool, param2: &str) {
 9408                    let var1 = "text";
 9409 9410            "#},
 9411            cx,
 9412        );
 9413    });
 9414
 9415    editor.update_in(cx, |editor, window, cx| {
 9416        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 9417    });
 9418    editor.update(cx, |editor, cx| {
 9419        assert_text_with_selections(
 9420            editor,
 9421            indoc! {r#"
 9422                use mod1::mod2::{mod3, «mod4ˇ»};
 9423
 9424                fn fn_1«ˇ(param1: bool, param2: &str)» {
 9425                    let var1 = "«ˇtext»";
 9426                }
 9427            "#},
 9428            cx,
 9429        );
 9430    });
 9431
 9432    editor.update_in(cx, |editor, window, cx| {
 9433        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 9434    });
 9435    editor.update(cx, |editor, cx| {
 9436        assert_text_with_selections(
 9437            editor,
 9438            indoc! {r#"
 9439                use mod1::mod2::{mod3, moˇd4};
 9440
 9441                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 9442                    let var1 = "teˇxt";
 9443                }
 9444            "#},
 9445            cx,
 9446        );
 9447    });
 9448
 9449    // Trying to shrink the selected syntax node one more time has no effect.
 9450    editor.update_in(cx, |editor, window, cx| {
 9451        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 9452    });
 9453    editor.update_in(cx, |editor, _, cx| {
 9454        assert_text_with_selections(
 9455            editor,
 9456            indoc! {r#"
 9457                use mod1::mod2::{mod3, moˇd4};
 9458
 9459                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 9460                    let var1 = "teˇxt";
 9461                }
 9462            "#},
 9463            cx,
 9464        );
 9465    });
 9466
 9467    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 9468    // a fold.
 9469    editor.update_in(cx, |editor, window, cx| {
 9470        editor.fold_creases(
 9471            vec![
 9472                Crease::simple(
 9473                    Point::new(0, 21)..Point::new(0, 24),
 9474                    FoldPlaceholder::test(),
 9475                ),
 9476                Crease::simple(
 9477                    Point::new(3, 20)..Point::new(3, 22),
 9478                    FoldPlaceholder::test(),
 9479                ),
 9480            ],
 9481            true,
 9482            window,
 9483            cx,
 9484        );
 9485        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9486    });
 9487    editor.update(cx, |editor, cx| {
 9488        assert_text_with_selections(
 9489            editor,
 9490            indoc! {r#"
 9491                use mod1::mod2::«{mod3, mod4}ˇ»;
 9492
 9493                fn fn_1«ˇ(param1: bool, param2: &str)» {
 9494                    let var1 = "«ˇtext»";
 9495                }
 9496            "#},
 9497            cx,
 9498        );
 9499    });
 9500}
 9501
 9502#[gpui::test]
 9503async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 9504    init_test(cx, |_| {});
 9505
 9506    let language = Arc::new(Language::new(
 9507        LanguageConfig::default(),
 9508        Some(tree_sitter_rust::LANGUAGE.into()),
 9509    ));
 9510
 9511    let text = "let a = 2;";
 9512
 9513    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9514    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9515    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9516
 9517    editor
 9518        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9519        .await;
 9520
 9521    // Test case 1: Cursor at end of word
 9522    editor.update_in(cx, |editor, window, cx| {
 9523        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9524            s.select_display_ranges([
 9525                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 9526            ]);
 9527        });
 9528    });
 9529    editor.update(cx, |editor, cx| {
 9530        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 9531    });
 9532    editor.update_in(cx, |editor, window, cx| {
 9533        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9534    });
 9535    editor.update(cx, |editor, cx| {
 9536        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 9537    });
 9538    editor.update_in(cx, |editor, window, cx| {
 9539        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9540    });
 9541    editor.update(cx, |editor, cx| {
 9542        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 9543    });
 9544
 9545    // Test case 2: Cursor at end of statement
 9546    editor.update_in(cx, |editor, window, cx| {
 9547        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9548            s.select_display_ranges([
 9549                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 9550            ]);
 9551        });
 9552    });
 9553    editor.update(cx, |editor, cx| {
 9554        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 9555    });
 9556    editor.update_in(cx, |editor, window, cx| {
 9557        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9558    });
 9559    editor.update(cx, |editor, cx| {
 9560        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 9561    });
 9562}
 9563
 9564#[gpui::test]
 9565async fn test_select_larger_syntax_node_for_cursor_at_symbol(cx: &mut TestAppContext) {
 9566    init_test(cx, |_| {});
 9567
 9568    let language = Arc::new(Language::new(
 9569        LanguageConfig {
 9570            name: "JavaScript".into(),
 9571            ..Default::default()
 9572        },
 9573        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 9574    ));
 9575
 9576    let text = r#"
 9577        let a = {
 9578            key: "value",
 9579        };
 9580    "#
 9581    .unindent();
 9582
 9583    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9584    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9585    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9586
 9587    editor
 9588        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9589        .await;
 9590
 9591    // Test case 1: Cursor after '{'
 9592    editor.update_in(cx, |editor, window, cx| {
 9593        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9594            s.select_display_ranges([
 9595                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 9)
 9596            ]);
 9597        });
 9598    });
 9599    editor.update(cx, |editor, cx| {
 9600        assert_text_with_selections(
 9601            editor,
 9602            indoc! {r#"
 9603                let a = {ˇ
 9604                    key: "value",
 9605                };
 9606            "#},
 9607            cx,
 9608        );
 9609    });
 9610    editor.update_in(cx, |editor, window, cx| {
 9611        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9612    });
 9613    editor.update(cx, |editor, cx| {
 9614        assert_text_with_selections(
 9615            editor,
 9616            indoc! {r#"
 9617                let a = «ˇ{
 9618                    key: "value",
 9619                }»;
 9620            "#},
 9621            cx,
 9622        );
 9623    });
 9624
 9625    // Test case 2: Cursor after ':'
 9626    editor.update_in(cx, |editor, window, cx| {
 9627        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9628            s.select_display_ranges([
 9629                DisplayPoint::new(DisplayRow(1), 8)..DisplayPoint::new(DisplayRow(1), 8)
 9630            ]);
 9631        });
 9632    });
 9633    editor.update(cx, |editor, cx| {
 9634        assert_text_with_selections(
 9635            editor,
 9636            indoc! {r#"
 9637                let a = {
 9638                    key:ˇ "value",
 9639                };
 9640            "#},
 9641            cx,
 9642        );
 9643    });
 9644    editor.update_in(cx, |editor, window, cx| {
 9645        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9646    });
 9647    editor.update(cx, |editor, cx| {
 9648        assert_text_with_selections(
 9649            editor,
 9650            indoc! {r#"
 9651                let a = {
 9652                    «ˇkey: "value"»,
 9653                };
 9654            "#},
 9655            cx,
 9656        );
 9657    });
 9658    editor.update_in(cx, |editor, window, cx| {
 9659        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9660    });
 9661    editor.update(cx, |editor, cx| {
 9662        assert_text_with_selections(
 9663            editor,
 9664            indoc! {r#"
 9665                let a = «ˇ{
 9666                    key: "value",
 9667                }»;
 9668            "#},
 9669            cx,
 9670        );
 9671    });
 9672
 9673    // Test case 3: Cursor after ','
 9674    editor.update_in(cx, |editor, window, cx| {
 9675        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9676            s.select_display_ranges([
 9677                DisplayPoint::new(DisplayRow(1), 17)..DisplayPoint::new(DisplayRow(1), 17)
 9678            ]);
 9679        });
 9680    });
 9681    editor.update(cx, |editor, cx| {
 9682        assert_text_with_selections(
 9683            editor,
 9684            indoc! {r#"
 9685                let a = {
 9686                    key: "value",ˇ
 9687                };
 9688            "#},
 9689            cx,
 9690        );
 9691    });
 9692    editor.update_in(cx, |editor, window, cx| {
 9693        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9694    });
 9695    editor.update(cx, |editor, cx| {
 9696        assert_text_with_selections(
 9697            editor,
 9698            indoc! {r#"
 9699                let a = «ˇ{
 9700                    key: "value",
 9701                }»;
 9702            "#},
 9703            cx,
 9704        );
 9705    });
 9706
 9707    // Test case 4: Cursor after ';'
 9708    editor.update_in(cx, |editor, window, cx| {
 9709        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9710            s.select_display_ranges([
 9711                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)
 9712            ]);
 9713        });
 9714    });
 9715    editor.update(cx, |editor, cx| {
 9716        assert_text_with_selections(
 9717            editor,
 9718            indoc! {r#"
 9719                let a = {
 9720                    key: "value",
 9721                };ˇ
 9722            "#},
 9723            cx,
 9724        );
 9725    });
 9726    editor.update_in(cx, |editor, window, cx| {
 9727        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9728    });
 9729    editor.update(cx, |editor, cx| {
 9730        assert_text_with_selections(
 9731            editor,
 9732            indoc! {r#"
 9733                «ˇlet a = {
 9734                    key: "value",
 9735                };
 9736                »"#},
 9737            cx,
 9738        );
 9739    });
 9740}
 9741
 9742#[gpui::test]
 9743async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 9744    init_test(cx, |_| {});
 9745
 9746    let language = Arc::new(Language::new(
 9747        LanguageConfig::default(),
 9748        Some(tree_sitter_rust::LANGUAGE.into()),
 9749    ));
 9750
 9751    let text = r#"
 9752        use mod1::mod2::{mod3, mod4};
 9753
 9754        fn fn_1(param1: bool, param2: &str) {
 9755            let var1 = "hello world";
 9756        }
 9757    "#
 9758    .unindent();
 9759
 9760    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9761    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9762    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9763
 9764    editor
 9765        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9766        .await;
 9767
 9768    // Test 1: Cursor on a letter of a string word
 9769    editor.update_in(cx, |editor, window, cx| {
 9770        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9771            s.select_display_ranges([
 9772                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 9773            ]);
 9774        });
 9775    });
 9776    editor.update_in(cx, |editor, window, cx| {
 9777        assert_text_with_selections(
 9778            editor,
 9779            indoc! {r#"
 9780                use mod1::mod2::{mod3, mod4};
 9781
 9782                fn fn_1(param1: bool, param2: &str) {
 9783                    let var1 = "hˇello world";
 9784                }
 9785            "#},
 9786            cx,
 9787        );
 9788        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9789        assert_text_with_selections(
 9790            editor,
 9791            indoc! {r#"
 9792                use mod1::mod2::{mod3, mod4};
 9793
 9794                fn fn_1(param1: bool, param2: &str) {
 9795                    let var1 = "«ˇhello» world";
 9796                }
 9797            "#},
 9798            cx,
 9799        );
 9800    });
 9801
 9802    // Test 2: Partial selection within a word
 9803    editor.update_in(cx, |editor, window, cx| {
 9804        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9805            s.select_display_ranges([
 9806                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 9807            ]);
 9808        });
 9809    });
 9810    editor.update_in(cx, |editor, window, cx| {
 9811        assert_text_with_selections(
 9812            editor,
 9813            indoc! {r#"
 9814                use mod1::mod2::{mod3, mod4};
 9815
 9816                fn fn_1(param1: bool, param2: &str) {
 9817                    let var1 = "h«elˇ»lo world";
 9818                }
 9819            "#},
 9820            cx,
 9821        );
 9822        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9823        assert_text_with_selections(
 9824            editor,
 9825            indoc! {r#"
 9826                use mod1::mod2::{mod3, mod4};
 9827
 9828                fn fn_1(param1: bool, param2: &str) {
 9829                    let var1 = "«ˇhello» world";
 9830                }
 9831            "#},
 9832            cx,
 9833        );
 9834    });
 9835
 9836    // Test 3: Complete word already selected
 9837    editor.update_in(cx, |editor, window, cx| {
 9838        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9839            s.select_display_ranges([
 9840                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 9841            ]);
 9842        });
 9843    });
 9844    editor.update_in(cx, |editor, window, cx| {
 9845        assert_text_with_selections(
 9846            editor,
 9847            indoc! {r#"
 9848                use mod1::mod2::{mod3, mod4};
 9849
 9850                fn fn_1(param1: bool, param2: &str) {
 9851                    let var1 = "«helloˇ» world";
 9852                }
 9853            "#},
 9854            cx,
 9855        );
 9856        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9857        assert_text_with_selections(
 9858            editor,
 9859            indoc! {r#"
 9860                use mod1::mod2::{mod3, mod4};
 9861
 9862                fn fn_1(param1: bool, param2: &str) {
 9863                    let var1 = "«hello worldˇ»";
 9864                }
 9865            "#},
 9866            cx,
 9867        );
 9868    });
 9869
 9870    // Test 4: Selection spanning across words
 9871    editor.update_in(cx, |editor, window, cx| {
 9872        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9873            s.select_display_ranges([
 9874                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 9875            ]);
 9876        });
 9877    });
 9878    editor.update_in(cx, |editor, window, cx| {
 9879        assert_text_with_selections(
 9880            editor,
 9881            indoc! {r#"
 9882                use mod1::mod2::{mod3, mod4};
 9883
 9884                fn fn_1(param1: bool, param2: &str) {
 9885                    let var1 = "hel«lo woˇ»rld";
 9886                }
 9887            "#},
 9888            cx,
 9889        );
 9890        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9891        assert_text_with_selections(
 9892            editor,
 9893            indoc! {r#"
 9894                use mod1::mod2::{mod3, mod4};
 9895
 9896                fn fn_1(param1: bool, param2: &str) {
 9897                    let var1 = "«ˇhello world»";
 9898                }
 9899            "#},
 9900            cx,
 9901        );
 9902    });
 9903
 9904    // Test 5: Expansion beyond string
 9905    editor.update_in(cx, |editor, window, cx| {
 9906        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9907        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9908        assert_text_with_selections(
 9909            editor,
 9910            indoc! {r#"
 9911                use mod1::mod2::{mod3, mod4};
 9912
 9913                fn fn_1(param1: bool, param2: &str) {
 9914                    «ˇlet var1 = "hello world";»
 9915                }
 9916            "#},
 9917            cx,
 9918        );
 9919    });
 9920}
 9921
 9922#[gpui::test]
 9923async fn test_unwrap_syntax_nodes(cx: &mut gpui::TestAppContext) {
 9924    init_test(cx, |_| {});
 9925
 9926    let mut cx = EditorTestContext::new(cx).await;
 9927
 9928    let language = Arc::new(Language::new(
 9929        LanguageConfig::default(),
 9930        Some(tree_sitter_rust::LANGUAGE.into()),
 9931    ));
 9932
 9933    cx.update_buffer(|buffer, cx| {
 9934        buffer.set_language(Some(language), cx);
 9935    });
 9936
 9937    cx.set_state(indoc! { r#"use mod1::{mod2::{«mod3ˇ», mod4}, mod5::{mod6, «mod7ˇ»}};"# });
 9938    cx.update_editor(|editor, window, cx| {
 9939        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9940    });
 9941
 9942    cx.assert_editor_state(indoc! { r#"use mod1::{mod2::«mod3ˇ», mod5::«mod7ˇ»};"# });
 9943
 9944    cx.set_state(indoc! { r#"fn a() {
 9945          // what
 9946          // a
 9947          // ˇlong
 9948          // method
 9949          // I
 9950          // sure
 9951          // hope
 9952          // it
 9953          // works
 9954    }"# });
 9955
 9956    let buffer = cx.update_multibuffer(|multibuffer, _| multibuffer.as_singleton().unwrap());
 9957    let multi_buffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
 9958    cx.update(|_, cx| {
 9959        multi_buffer.update(cx, |multi_buffer, cx| {
 9960            multi_buffer.set_excerpts_for_path(
 9961                PathKey::for_buffer(&buffer, cx),
 9962                buffer,
 9963                [Point::new(1, 0)..Point::new(1, 0)],
 9964                3,
 9965                cx,
 9966            );
 9967        });
 9968    });
 9969
 9970    let editor2 = cx.new_window_entity(|window, cx| {
 9971        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
 9972    });
 9973
 9974    let mut cx = EditorTestContext::for_editor_in(editor2, &mut cx).await;
 9975    cx.update_editor(|editor, window, cx| {
 9976        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 9977            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)]);
 9978        })
 9979    });
 9980
 9981    cx.assert_editor_state(indoc! { "
 9982        fn a() {
 9983              // what
 9984              // a
 9985        ˇ      // long
 9986              // method"});
 9987
 9988    cx.update_editor(|editor, window, cx| {
 9989        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9990    });
 9991
 9992    // Although we could potentially make the action work when the syntax node
 9993    // is half-hidden, it seems a bit dangerous as you can't easily tell what it
 9994    // did. Maybe we could also expand the excerpt to contain the range?
 9995    cx.assert_editor_state(indoc! { "
 9996        fn a() {
 9997              // what
 9998              // a
 9999        ˇ      // long
10000              // method"});
10001}
10002
10003#[gpui::test]
10004async fn test_fold_function_bodies(cx: &mut TestAppContext) {
10005    init_test(cx, |_| {});
10006
10007    let base_text = r#"
10008        impl A {
10009            // this is an uncommitted comment
10010
10011            fn b() {
10012                c();
10013            }
10014
10015            // this is another uncommitted comment
10016
10017            fn d() {
10018                // e
10019                // f
10020            }
10021        }
10022
10023        fn g() {
10024            // h
10025        }
10026    "#
10027    .unindent();
10028
10029    let text = r#"
10030        ˇimpl A {
10031
10032            fn b() {
10033                c();
10034            }
10035
10036            fn d() {
10037                // e
10038                // f
10039            }
10040        }
10041
10042        fn g() {
10043            // h
10044        }
10045    "#
10046    .unindent();
10047
10048    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
10049    cx.set_state(&text);
10050    cx.set_head_text(&base_text);
10051    cx.update_editor(|editor, window, cx| {
10052        editor.expand_all_diff_hunks(&Default::default(), window, cx);
10053    });
10054
10055    cx.assert_state_with_diff(
10056        "
10057        ˇimpl A {
10058      -     // this is an uncommitted comment
10059
10060            fn b() {
10061                c();
10062            }
10063
10064      -     // this is another uncommitted comment
10065      -
10066            fn d() {
10067                // e
10068                // f
10069            }
10070        }
10071
10072        fn g() {
10073            // h
10074        }
10075    "
10076        .unindent(),
10077    );
10078
10079    let expected_display_text = "
10080        impl A {
10081            // this is an uncommitted comment
10082
10083            fn b() {
1008410085            }
10086
10087            // this is another uncommitted comment
10088
10089            fn d() {
1009010091            }
10092        }
10093
10094        fn g() {
1009510096        }
10097        "
10098    .unindent();
10099
10100    cx.update_editor(|editor, window, cx| {
10101        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
10102        assert_eq!(editor.display_text(cx), expected_display_text);
10103    });
10104}
10105
10106#[gpui::test]
10107async fn test_autoindent(cx: &mut TestAppContext) {
10108    init_test(cx, |_| {});
10109
10110    let language = Arc::new(
10111        Language::new(
10112            LanguageConfig {
10113                brackets: BracketPairConfig {
10114                    pairs: vec![
10115                        BracketPair {
10116                            start: "{".to_string(),
10117                            end: "}".to_string(),
10118                            close: false,
10119                            surround: false,
10120                            newline: true,
10121                        },
10122                        BracketPair {
10123                            start: "(".to_string(),
10124                            end: ")".to_string(),
10125                            close: false,
10126                            surround: false,
10127                            newline: true,
10128                        },
10129                    ],
10130                    ..Default::default()
10131                },
10132                ..Default::default()
10133            },
10134            Some(tree_sitter_rust::LANGUAGE.into()),
10135        )
10136        .with_indents_query(
10137            r#"
10138                (_ "(" ")" @end) @indent
10139                (_ "{" "}" @end) @indent
10140            "#,
10141        )
10142        .unwrap(),
10143    );
10144
10145    let text = "fn a() {}";
10146
10147    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10148    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10149    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10150    editor
10151        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10152        .await;
10153
10154    editor.update_in(cx, |editor, window, cx| {
10155        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10156            s.select_ranges([
10157                MultiBufferOffset(5)..MultiBufferOffset(5),
10158                MultiBufferOffset(8)..MultiBufferOffset(8),
10159                MultiBufferOffset(9)..MultiBufferOffset(9),
10160            ])
10161        });
10162        editor.newline(&Newline, window, cx);
10163        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
10164        assert_eq!(
10165            editor.selections.ranges(&editor.display_snapshot(cx)),
10166            &[
10167                Point::new(1, 4)..Point::new(1, 4),
10168                Point::new(3, 4)..Point::new(3, 4),
10169                Point::new(5, 0)..Point::new(5, 0)
10170            ]
10171        );
10172    });
10173}
10174
10175#[gpui::test]
10176async fn test_autoindent_disabled(cx: &mut TestAppContext) {
10177    init_test(cx, |settings| settings.defaults.auto_indent = Some(false));
10178
10179    let language = Arc::new(
10180        Language::new(
10181            LanguageConfig {
10182                brackets: BracketPairConfig {
10183                    pairs: vec![
10184                        BracketPair {
10185                            start: "{".to_string(),
10186                            end: "}".to_string(),
10187                            close: false,
10188                            surround: false,
10189                            newline: true,
10190                        },
10191                        BracketPair {
10192                            start: "(".to_string(),
10193                            end: ")".to_string(),
10194                            close: false,
10195                            surround: false,
10196                            newline: true,
10197                        },
10198                    ],
10199                    ..Default::default()
10200                },
10201                ..Default::default()
10202            },
10203            Some(tree_sitter_rust::LANGUAGE.into()),
10204        )
10205        .with_indents_query(
10206            r#"
10207                (_ "(" ")" @end) @indent
10208                (_ "{" "}" @end) @indent
10209            "#,
10210        )
10211        .unwrap(),
10212    );
10213
10214    let text = "fn a() {}";
10215
10216    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10217    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10218    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10219    editor
10220        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10221        .await;
10222
10223    editor.update_in(cx, |editor, window, cx| {
10224        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10225            s.select_ranges([
10226                MultiBufferOffset(5)..MultiBufferOffset(5),
10227                MultiBufferOffset(8)..MultiBufferOffset(8),
10228                MultiBufferOffset(9)..MultiBufferOffset(9),
10229            ])
10230        });
10231        editor.newline(&Newline, window, cx);
10232        assert_eq!(
10233            editor.text(cx),
10234            indoc!(
10235                "
10236                fn a(
10237
10238                ) {
10239
10240                }
10241                "
10242            )
10243        );
10244        assert_eq!(
10245            editor.selections.ranges(&editor.display_snapshot(cx)),
10246            &[
10247                Point::new(1, 0)..Point::new(1, 0),
10248                Point::new(3, 0)..Point::new(3, 0),
10249                Point::new(5, 0)..Point::new(5, 0)
10250            ]
10251        );
10252    });
10253}
10254
10255#[gpui::test]
10256async fn test_autoindent_disabled_with_nested_language(cx: &mut TestAppContext) {
10257    init_test(cx, |settings| {
10258        settings.defaults.auto_indent = Some(true);
10259        settings.languages.0.insert(
10260            "python".into(),
10261            LanguageSettingsContent {
10262                auto_indent: Some(false),
10263                ..Default::default()
10264            },
10265        );
10266    });
10267
10268    let mut cx = EditorTestContext::new(cx).await;
10269
10270    let injected_language = Arc::new(
10271        Language::new(
10272            LanguageConfig {
10273                brackets: BracketPairConfig {
10274                    pairs: vec![
10275                        BracketPair {
10276                            start: "{".to_string(),
10277                            end: "}".to_string(),
10278                            close: false,
10279                            surround: false,
10280                            newline: true,
10281                        },
10282                        BracketPair {
10283                            start: "(".to_string(),
10284                            end: ")".to_string(),
10285                            close: true,
10286                            surround: false,
10287                            newline: true,
10288                        },
10289                    ],
10290                    ..Default::default()
10291                },
10292                name: "python".into(),
10293                ..Default::default()
10294            },
10295            Some(tree_sitter_python::LANGUAGE.into()),
10296        )
10297        .with_indents_query(
10298            r#"
10299                (_ "(" ")" @end) @indent
10300                (_ "{" "}" @end) @indent
10301            "#,
10302        )
10303        .unwrap(),
10304    );
10305
10306    let language = Arc::new(
10307        Language::new(
10308            LanguageConfig {
10309                brackets: BracketPairConfig {
10310                    pairs: vec![
10311                        BracketPair {
10312                            start: "{".to_string(),
10313                            end: "}".to_string(),
10314                            close: false,
10315                            surround: false,
10316                            newline: true,
10317                        },
10318                        BracketPair {
10319                            start: "(".to_string(),
10320                            end: ")".to_string(),
10321                            close: true,
10322                            surround: false,
10323                            newline: true,
10324                        },
10325                    ],
10326                    ..Default::default()
10327                },
10328                name: LanguageName::new_static("rust"),
10329                ..Default::default()
10330            },
10331            Some(tree_sitter_rust::LANGUAGE.into()),
10332        )
10333        .with_indents_query(
10334            r#"
10335                (_ "(" ")" @end) @indent
10336                (_ "{" "}" @end) @indent
10337            "#,
10338        )
10339        .unwrap()
10340        .with_injection_query(
10341            r#"
10342            (macro_invocation
10343                macro: (identifier) @_macro_name
10344                (token_tree) @injection.content
10345                (#set! injection.language "python"))
10346           "#,
10347        )
10348        .unwrap(),
10349    );
10350
10351    cx.language_registry().add(injected_language);
10352    cx.language_registry().add(language.clone());
10353
10354    cx.update_buffer(|buffer, cx| {
10355        buffer.set_language(Some(language), cx);
10356    });
10357
10358    cx.set_state(r#"struct A {ˇ}"#);
10359
10360    cx.update_editor(|editor, window, cx| {
10361        editor.newline(&Default::default(), window, cx);
10362    });
10363
10364    cx.assert_editor_state(indoc!(
10365        "struct A {
10366            ˇ
10367        }"
10368    ));
10369
10370    cx.set_state(r#"select_biased!(ˇ)"#);
10371
10372    cx.update_editor(|editor, window, cx| {
10373        editor.newline(&Default::default(), window, cx);
10374        editor.handle_input("def ", window, cx);
10375        editor.handle_input("(", window, cx);
10376        editor.newline(&Default::default(), window, cx);
10377        editor.handle_input("a", window, cx);
10378    });
10379
10380    cx.assert_editor_state(indoc!(
10381        "select_biased!(
10382        def (
1038310384        )
10385        )"
10386    ));
10387}
10388
10389#[gpui::test]
10390async fn test_autoindent_selections(cx: &mut TestAppContext) {
10391    init_test(cx, |_| {});
10392
10393    {
10394        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
10395        cx.set_state(indoc! {"
10396            impl A {
10397
10398                fn b() {}
10399
10400            «fn c() {
10401
10402            }ˇ»
10403            }
10404        "});
10405
10406        cx.update_editor(|editor, window, cx| {
10407            editor.autoindent(&Default::default(), window, cx);
10408        });
10409        cx.wait_for_autoindent_applied().await;
10410
10411        cx.assert_editor_state(indoc! {"
10412            impl A {
10413
10414                fn b() {}
10415
10416                «fn c() {
10417
10418                }ˇ»
10419            }
10420        "});
10421    }
10422
10423    {
10424        let mut cx = EditorTestContext::new_multibuffer(
10425            cx,
10426            [indoc! { "
10427                impl A {
10428                «
10429                // a
10430                fn b(){}
10431                »
10432                «
10433                    }
10434                    fn c(){}
10435                »
10436            "}],
10437        );
10438
10439        let buffer = cx.update_editor(|editor, _, cx| {
10440            let buffer = editor.buffer().update(cx, |buffer, _| {
10441                buffer.all_buffers().iter().next().unwrap().clone()
10442            });
10443            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
10444            buffer
10445        });
10446
10447        cx.run_until_parked();
10448        cx.update_editor(|editor, window, cx| {
10449            editor.select_all(&Default::default(), window, cx);
10450            editor.autoindent(&Default::default(), window, cx)
10451        });
10452        cx.run_until_parked();
10453
10454        cx.update(|_, cx| {
10455            assert_eq!(
10456                buffer.read(cx).text(),
10457                indoc! { "
10458                    impl A {
10459
10460                        // a
10461                        fn b(){}
10462
10463
10464                    }
10465                    fn c(){}
10466
10467                " }
10468            )
10469        });
10470    }
10471}
10472
10473#[gpui::test]
10474async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
10475    init_test(cx, |_| {});
10476
10477    let mut cx = EditorTestContext::new(cx).await;
10478
10479    let language = Arc::new(Language::new(
10480        LanguageConfig {
10481            brackets: BracketPairConfig {
10482                pairs: vec![
10483                    BracketPair {
10484                        start: "{".to_string(),
10485                        end: "}".to_string(),
10486                        close: true,
10487                        surround: true,
10488                        newline: true,
10489                    },
10490                    BracketPair {
10491                        start: "(".to_string(),
10492                        end: ")".to_string(),
10493                        close: true,
10494                        surround: true,
10495                        newline: true,
10496                    },
10497                    BracketPair {
10498                        start: "/*".to_string(),
10499                        end: " */".to_string(),
10500                        close: true,
10501                        surround: true,
10502                        newline: true,
10503                    },
10504                    BracketPair {
10505                        start: "[".to_string(),
10506                        end: "]".to_string(),
10507                        close: false,
10508                        surround: false,
10509                        newline: true,
10510                    },
10511                    BracketPair {
10512                        start: "\"".to_string(),
10513                        end: "\"".to_string(),
10514                        close: true,
10515                        surround: true,
10516                        newline: false,
10517                    },
10518                    BracketPair {
10519                        start: "<".to_string(),
10520                        end: ">".to_string(),
10521                        close: false,
10522                        surround: true,
10523                        newline: true,
10524                    },
10525                ],
10526                ..Default::default()
10527            },
10528            autoclose_before: "})]".to_string(),
10529            ..Default::default()
10530        },
10531        Some(tree_sitter_rust::LANGUAGE.into()),
10532    ));
10533
10534    cx.language_registry().add(language.clone());
10535    cx.update_buffer(|buffer, cx| {
10536        buffer.set_language(Some(language), cx);
10537    });
10538
10539    cx.set_state(
10540        &r#"
10541            🏀ˇ
10542            εˇ
10543            ❤️ˇ
10544        "#
10545        .unindent(),
10546    );
10547
10548    // autoclose multiple nested brackets at multiple cursors
10549    cx.update_editor(|editor, window, cx| {
10550        editor.handle_input("{", window, cx);
10551        editor.handle_input("{", window, cx);
10552        editor.handle_input("{", window, cx);
10553    });
10554    cx.assert_editor_state(
10555        &"
10556            🏀{{{ˇ}}}
10557            ε{{{ˇ}}}
10558            ❤️{{{ˇ}}}
10559        "
10560        .unindent(),
10561    );
10562
10563    // insert a different closing bracket
10564    cx.update_editor(|editor, window, cx| {
10565        editor.handle_input(")", window, cx);
10566    });
10567    cx.assert_editor_state(
10568        &"
10569            🏀{{{)ˇ}}}
10570            ε{{{)ˇ}}}
10571            ❤️{{{)ˇ}}}
10572        "
10573        .unindent(),
10574    );
10575
10576    // skip over the auto-closed brackets when typing a closing bracket
10577    cx.update_editor(|editor, window, cx| {
10578        editor.move_right(&MoveRight, window, cx);
10579        editor.handle_input("}", window, cx);
10580        editor.handle_input("}", window, cx);
10581        editor.handle_input("}", window, cx);
10582    });
10583    cx.assert_editor_state(
10584        &"
10585            🏀{{{)}}}}ˇ
10586            ε{{{)}}}}ˇ
10587            ❤️{{{)}}}}ˇ
10588        "
10589        .unindent(),
10590    );
10591
10592    // autoclose multi-character pairs
10593    cx.set_state(
10594        &"
10595            ˇ
10596            ˇ
10597        "
10598        .unindent(),
10599    );
10600    cx.update_editor(|editor, window, cx| {
10601        editor.handle_input("/", window, cx);
10602        editor.handle_input("*", window, cx);
10603    });
10604    cx.assert_editor_state(
10605        &"
10606            /*ˇ */
10607            /*ˇ */
10608        "
10609        .unindent(),
10610    );
10611
10612    // one cursor autocloses a multi-character pair, one cursor
10613    // does not autoclose.
10614    cx.set_state(
10615        &"
1061610617            ˇ
10618        "
10619        .unindent(),
10620    );
10621    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
10622    cx.assert_editor_state(
10623        &"
10624            /*ˇ */
1062510626        "
10627        .unindent(),
10628    );
10629
10630    // Don't autoclose if the next character isn't whitespace and isn't
10631    // listed in the language's "autoclose_before" section.
10632    cx.set_state("ˇa b");
10633    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10634    cx.assert_editor_state("{ˇa b");
10635
10636    // Don't autoclose if `close` is false for the bracket pair
10637    cx.set_state("ˇ");
10638    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
10639    cx.assert_editor_state("");
10640
10641    // Surround with brackets if text is selected
10642    cx.set_state("«aˇ» b");
10643    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10644    cx.assert_editor_state("{«aˇ»} b");
10645
10646    // Autoclose when not immediately after a word character
10647    cx.set_state("a ˇ");
10648    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10649    cx.assert_editor_state("a \"ˇ\"");
10650
10651    // Autoclose pair where the start and end characters are the same
10652    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10653    cx.assert_editor_state("a \"\"ˇ");
10654
10655    // Don't autoclose when immediately after a word character
10656    cx.set_state("");
10657    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10658    cx.assert_editor_state("a\"ˇ");
10659
10660    // Do autoclose when after a non-word character
10661    cx.set_state("");
10662    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10663    cx.assert_editor_state("{\"ˇ\"");
10664
10665    // Non identical pairs autoclose regardless of preceding character
10666    cx.set_state("");
10667    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10668    cx.assert_editor_state("a{ˇ}");
10669
10670    // Don't autoclose pair if autoclose is disabled
10671    cx.set_state("ˇ");
10672    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
10673    cx.assert_editor_state("");
10674
10675    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
10676    cx.set_state("«aˇ» b");
10677    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
10678    cx.assert_editor_state("<«aˇ»> b");
10679}
10680
10681#[gpui::test]
10682async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
10683    init_test(cx, |settings| {
10684        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
10685    });
10686
10687    let mut cx = EditorTestContext::new(cx).await;
10688
10689    let language = Arc::new(Language::new(
10690        LanguageConfig {
10691            brackets: BracketPairConfig {
10692                pairs: vec![
10693                    BracketPair {
10694                        start: "{".to_string(),
10695                        end: "}".to_string(),
10696                        close: true,
10697                        surround: true,
10698                        newline: true,
10699                    },
10700                    BracketPair {
10701                        start: "(".to_string(),
10702                        end: ")".to_string(),
10703                        close: true,
10704                        surround: true,
10705                        newline: true,
10706                    },
10707                    BracketPair {
10708                        start: "[".to_string(),
10709                        end: "]".to_string(),
10710                        close: false,
10711                        surround: false,
10712                        newline: true,
10713                    },
10714                ],
10715                ..Default::default()
10716            },
10717            autoclose_before: "})]".to_string(),
10718            ..Default::default()
10719        },
10720        Some(tree_sitter_rust::LANGUAGE.into()),
10721    ));
10722
10723    cx.language_registry().add(language.clone());
10724    cx.update_buffer(|buffer, cx| {
10725        buffer.set_language(Some(language), cx);
10726    });
10727
10728    cx.set_state(
10729        &"
10730            ˇ
10731            ˇ
10732            ˇ
10733        "
10734        .unindent(),
10735    );
10736
10737    // ensure only matching closing brackets are skipped over
10738    cx.update_editor(|editor, window, cx| {
10739        editor.handle_input("}", window, cx);
10740        editor.move_left(&MoveLeft, window, cx);
10741        editor.handle_input(")", window, cx);
10742        editor.move_left(&MoveLeft, window, cx);
10743    });
10744    cx.assert_editor_state(
10745        &"
10746            ˇ)}
10747            ˇ)}
10748            ˇ)}
10749        "
10750        .unindent(),
10751    );
10752
10753    // skip-over closing brackets at multiple cursors
10754    cx.update_editor(|editor, window, cx| {
10755        editor.handle_input(")", window, cx);
10756        editor.handle_input("}", window, cx);
10757    });
10758    cx.assert_editor_state(
10759        &"
10760            )}ˇ
10761            )}ˇ
10762            )}ˇ
10763        "
10764        .unindent(),
10765    );
10766
10767    // ignore non-close brackets
10768    cx.update_editor(|editor, window, cx| {
10769        editor.handle_input("]", window, cx);
10770        editor.move_left(&MoveLeft, window, cx);
10771        editor.handle_input("]", window, cx);
10772    });
10773    cx.assert_editor_state(
10774        &"
10775            )}]ˇ]
10776            )}]ˇ]
10777            )}]ˇ]
10778        "
10779        .unindent(),
10780    );
10781}
10782
10783#[gpui::test]
10784async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
10785    init_test(cx, |_| {});
10786
10787    let mut cx = EditorTestContext::new(cx).await;
10788
10789    let html_language = Arc::new(
10790        Language::new(
10791            LanguageConfig {
10792                name: "HTML".into(),
10793                brackets: BracketPairConfig {
10794                    pairs: vec![
10795                        BracketPair {
10796                            start: "<".into(),
10797                            end: ">".into(),
10798                            close: true,
10799                            ..Default::default()
10800                        },
10801                        BracketPair {
10802                            start: "{".into(),
10803                            end: "}".into(),
10804                            close: true,
10805                            ..Default::default()
10806                        },
10807                        BracketPair {
10808                            start: "(".into(),
10809                            end: ")".into(),
10810                            close: true,
10811                            ..Default::default()
10812                        },
10813                    ],
10814                    ..Default::default()
10815                },
10816                autoclose_before: "})]>".into(),
10817                ..Default::default()
10818            },
10819            Some(tree_sitter_html::LANGUAGE.into()),
10820        )
10821        .with_injection_query(
10822            r#"
10823            (script_element
10824                (raw_text) @injection.content
10825                (#set! injection.language "javascript"))
10826            "#,
10827        )
10828        .unwrap(),
10829    );
10830
10831    let javascript_language = Arc::new(Language::new(
10832        LanguageConfig {
10833            name: "JavaScript".into(),
10834            brackets: BracketPairConfig {
10835                pairs: vec![
10836                    BracketPair {
10837                        start: "/*".into(),
10838                        end: " */".into(),
10839                        close: true,
10840                        ..Default::default()
10841                    },
10842                    BracketPair {
10843                        start: "{".into(),
10844                        end: "}".into(),
10845                        close: true,
10846                        ..Default::default()
10847                    },
10848                    BracketPair {
10849                        start: "(".into(),
10850                        end: ")".into(),
10851                        close: true,
10852                        ..Default::default()
10853                    },
10854                ],
10855                ..Default::default()
10856            },
10857            autoclose_before: "})]>".into(),
10858            ..Default::default()
10859        },
10860        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10861    ));
10862
10863    cx.language_registry().add(html_language.clone());
10864    cx.language_registry().add(javascript_language);
10865    cx.executor().run_until_parked();
10866
10867    cx.update_buffer(|buffer, cx| {
10868        buffer.set_language(Some(html_language), cx);
10869    });
10870
10871    cx.set_state(
10872        &r#"
10873            <body>ˇ
10874                <script>
10875                    var x = 1;ˇ
10876                </script>
10877            </body>ˇ
10878        "#
10879        .unindent(),
10880    );
10881
10882    // Precondition: different languages are active at different locations.
10883    cx.update_editor(|editor, window, cx| {
10884        let snapshot = editor.snapshot(window, cx);
10885        let cursors = editor
10886            .selections
10887            .ranges::<MultiBufferOffset>(&editor.display_snapshot(cx));
10888        let languages = cursors
10889            .iter()
10890            .map(|c| snapshot.language_at(c.start).unwrap().name())
10891            .collect::<Vec<_>>();
10892        assert_eq!(
10893            languages,
10894            &["HTML".into(), "JavaScript".into(), "HTML".into()]
10895        );
10896    });
10897
10898    // Angle brackets autoclose in HTML, but not JavaScript.
10899    cx.update_editor(|editor, window, cx| {
10900        editor.handle_input("<", window, cx);
10901        editor.handle_input("a", window, cx);
10902    });
10903    cx.assert_editor_state(
10904        &r#"
10905            <body><aˇ>
10906                <script>
10907                    var x = 1;<aˇ
10908                </script>
10909            </body><aˇ>
10910        "#
10911        .unindent(),
10912    );
10913
10914    // Curly braces and parens autoclose in both HTML and JavaScript.
10915    cx.update_editor(|editor, window, cx| {
10916        editor.handle_input(" b=", window, cx);
10917        editor.handle_input("{", window, cx);
10918        editor.handle_input("c", window, cx);
10919        editor.handle_input("(", window, cx);
10920    });
10921    cx.assert_editor_state(
10922        &r#"
10923            <body><a b={c(ˇ)}>
10924                <script>
10925                    var x = 1;<a b={c(ˇ)}
10926                </script>
10927            </body><a b={c(ˇ)}>
10928        "#
10929        .unindent(),
10930    );
10931
10932    // Brackets that were already autoclosed are skipped.
10933    cx.update_editor(|editor, window, cx| {
10934        editor.handle_input(")", window, cx);
10935        editor.handle_input("d", window, cx);
10936        editor.handle_input("}", window, cx);
10937    });
10938    cx.assert_editor_state(
10939        &r#"
10940            <body><a b={c()d}ˇ>
10941                <script>
10942                    var x = 1;<a b={c()d}ˇ
10943                </script>
10944            </body><a b={c()d}ˇ>
10945        "#
10946        .unindent(),
10947    );
10948    cx.update_editor(|editor, window, cx| {
10949        editor.handle_input(">", window, cx);
10950    });
10951    cx.assert_editor_state(
10952        &r#"
10953            <body><a b={c()d}>ˇ
10954                <script>
10955                    var x = 1;<a b={c()d}>ˇ
10956                </script>
10957            </body><a b={c()d}>ˇ
10958        "#
10959        .unindent(),
10960    );
10961
10962    // Reset
10963    cx.set_state(
10964        &r#"
10965            <body>ˇ
10966                <script>
10967                    var x = 1;ˇ
10968                </script>
10969            </body>ˇ
10970        "#
10971        .unindent(),
10972    );
10973
10974    cx.update_editor(|editor, window, cx| {
10975        editor.handle_input("<", window, cx);
10976    });
10977    cx.assert_editor_state(
10978        &r#"
10979            <body><ˇ>
10980                <script>
10981                    var x = 1;<ˇ
10982                </script>
10983            </body><ˇ>
10984        "#
10985        .unindent(),
10986    );
10987
10988    // When backspacing, the closing angle brackets are removed.
10989    cx.update_editor(|editor, window, cx| {
10990        editor.backspace(&Backspace, window, cx);
10991    });
10992    cx.assert_editor_state(
10993        &r#"
10994            <body>ˇ
10995                <script>
10996                    var x = 1;ˇ
10997                </script>
10998            </body>ˇ
10999        "#
11000        .unindent(),
11001    );
11002
11003    // Block comments autoclose in JavaScript, but not HTML.
11004    cx.update_editor(|editor, window, cx| {
11005        editor.handle_input("/", window, cx);
11006        editor.handle_input("*", window, cx);
11007    });
11008    cx.assert_editor_state(
11009        &r#"
11010            <body>/*ˇ
11011                <script>
11012                    var x = 1;/*ˇ */
11013                </script>
11014            </body>/*ˇ
11015        "#
11016        .unindent(),
11017    );
11018}
11019
11020#[gpui::test]
11021async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
11022    init_test(cx, |_| {});
11023
11024    let mut cx = EditorTestContext::new(cx).await;
11025
11026    let rust_language = Arc::new(
11027        Language::new(
11028            LanguageConfig {
11029                name: "Rust".into(),
11030                brackets: serde_json::from_value(json!([
11031                    { "start": "{", "end": "}", "close": true, "newline": true },
11032                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
11033                ]))
11034                .unwrap(),
11035                autoclose_before: "})]>".into(),
11036                ..Default::default()
11037            },
11038            Some(tree_sitter_rust::LANGUAGE.into()),
11039        )
11040        .with_override_query("(string_literal) @string")
11041        .unwrap(),
11042    );
11043
11044    cx.language_registry().add(rust_language.clone());
11045    cx.update_buffer(|buffer, cx| {
11046        buffer.set_language(Some(rust_language), cx);
11047    });
11048
11049    cx.set_state(
11050        &r#"
11051            let x = ˇ
11052        "#
11053        .unindent(),
11054    );
11055
11056    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
11057    cx.update_editor(|editor, window, cx| {
11058        editor.handle_input("\"", window, cx);
11059    });
11060    cx.assert_editor_state(
11061        &r#"
11062            let x = "ˇ"
11063        "#
11064        .unindent(),
11065    );
11066
11067    // Inserting another quotation mark. The cursor moves across the existing
11068    // automatically-inserted quotation mark.
11069    cx.update_editor(|editor, window, cx| {
11070        editor.handle_input("\"", window, cx);
11071    });
11072    cx.assert_editor_state(
11073        &r#"
11074            let x = ""ˇ
11075        "#
11076        .unindent(),
11077    );
11078
11079    // Reset
11080    cx.set_state(
11081        &r#"
11082            let x = ˇ
11083        "#
11084        .unindent(),
11085    );
11086
11087    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
11088    cx.update_editor(|editor, window, cx| {
11089        editor.handle_input("\"", window, cx);
11090        editor.handle_input(" ", window, cx);
11091        editor.move_left(&Default::default(), window, cx);
11092        editor.handle_input("\\", window, cx);
11093        editor.handle_input("\"", window, cx);
11094    });
11095    cx.assert_editor_state(
11096        &r#"
11097            let x = "\"ˇ "
11098        "#
11099        .unindent(),
11100    );
11101
11102    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
11103    // mark. Nothing is inserted.
11104    cx.update_editor(|editor, window, cx| {
11105        editor.move_right(&Default::default(), window, cx);
11106        editor.handle_input("\"", window, cx);
11107    });
11108    cx.assert_editor_state(
11109        &r#"
11110            let x = "\" "ˇ
11111        "#
11112        .unindent(),
11113    );
11114}
11115
11116#[gpui::test]
11117async fn test_autoclose_quotes_with_scope_awareness(cx: &mut TestAppContext) {
11118    init_test(cx, |_| {});
11119
11120    let mut cx = EditorTestContext::new(cx).await;
11121    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
11122
11123    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
11124
11125    // Double quote inside single-quoted string
11126    cx.set_state(indoc! {r#"
11127        def main():
11128            items = ['"', ˇ]
11129    "#});
11130    cx.update_editor(|editor, window, cx| {
11131        editor.handle_input("\"", window, cx);
11132    });
11133    cx.assert_editor_state(indoc! {r#"
11134        def main():
11135            items = ['"', "ˇ"]
11136    "#});
11137
11138    // Two double quotes inside single-quoted string
11139    cx.set_state(indoc! {r#"
11140        def main():
11141            items = ['""', ˇ]
11142    "#});
11143    cx.update_editor(|editor, window, cx| {
11144        editor.handle_input("\"", window, cx);
11145    });
11146    cx.assert_editor_state(indoc! {r#"
11147        def main():
11148            items = ['""', "ˇ"]
11149    "#});
11150
11151    // Single quote inside double-quoted string
11152    cx.set_state(indoc! {r#"
11153        def main():
11154            items = ["'", ˇ]
11155    "#});
11156    cx.update_editor(|editor, window, cx| {
11157        editor.handle_input("'", window, cx);
11158    });
11159    cx.assert_editor_state(indoc! {r#"
11160        def main():
11161            items = ["'", 'ˇ']
11162    "#});
11163
11164    // Two single quotes inside double-quoted string
11165    cx.set_state(indoc! {r#"
11166        def main():
11167            items = ["''", ˇ]
11168    "#});
11169    cx.update_editor(|editor, window, cx| {
11170        editor.handle_input("'", window, cx);
11171    });
11172    cx.assert_editor_state(indoc! {r#"
11173        def main():
11174            items = ["''", 'ˇ']
11175    "#});
11176
11177    // Mixed quotes on same line
11178    cx.set_state(indoc! {r#"
11179        def main():
11180            items = ['"""', "'''''", ˇ]
11181    "#});
11182    cx.update_editor(|editor, window, cx| {
11183        editor.handle_input("\"", window, cx);
11184    });
11185    cx.assert_editor_state(indoc! {r#"
11186        def main():
11187            items = ['"""', "'''''", "ˇ"]
11188    "#});
11189    cx.update_editor(|editor, window, cx| {
11190        editor.move_right(&MoveRight, window, cx);
11191    });
11192    cx.update_editor(|editor, window, cx| {
11193        editor.handle_input(", ", window, cx);
11194    });
11195    cx.update_editor(|editor, window, cx| {
11196        editor.handle_input("'", window, cx);
11197    });
11198    cx.assert_editor_state(indoc! {r#"
11199        def main():
11200            items = ['"""', "'''''", "", 'ˇ']
11201    "#});
11202}
11203
11204#[gpui::test]
11205async fn test_autoclose_quotes_with_multibyte_characters(cx: &mut TestAppContext) {
11206    init_test(cx, |_| {});
11207
11208    let mut cx = EditorTestContext::new(cx).await;
11209    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
11210    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
11211
11212    cx.set_state(indoc! {r#"
11213        def main():
11214            items = ["🎉", ˇ]
11215    "#});
11216    cx.update_editor(|editor, window, cx| {
11217        editor.handle_input("\"", window, cx);
11218    });
11219    cx.assert_editor_state(indoc! {r#"
11220        def main():
11221            items = ["🎉", "ˇ"]
11222    "#});
11223}
11224
11225#[gpui::test]
11226async fn test_surround_with_pair(cx: &mut TestAppContext) {
11227    init_test(cx, |_| {});
11228
11229    let language = Arc::new(Language::new(
11230        LanguageConfig {
11231            brackets: BracketPairConfig {
11232                pairs: vec![
11233                    BracketPair {
11234                        start: "{".to_string(),
11235                        end: "}".to_string(),
11236                        close: true,
11237                        surround: true,
11238                        newline: true,
11239                    },
11240                    BracketPair {
11241                        start: "/* ".to_string(),
11242                        end: "*/".to_string(),
11243                        close: true,
11244                        surround: true,
11245                        ..Default::default()
11246                    },
11247                ],
11248                ..Default::default()
11249            },
11250            ..Default::default()
11251        },
11252        Some(tree_sitter_rust::LANGUAGE.into()),
11253    ));
11254
11255    let text = r#"
11256        a
11257        b
11258        c
11259    "#
11260    .unindent();
11261
11262    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
11263    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11264    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11265    editor
11266        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
11267        .await;
11268
11269    editor.update_in(cx, |editor, window, cx| {
11270        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11271            s.select_display_ranges([
11272                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
11273                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
11274                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
11275            ])
11276        });
11277
11278        editor.handle_input("{", window, cx);
11279        editor.handle_input("{", window, cx);
11280        editor.handle_input("{", window, cx);
11281        assert_eq!(
11282            editor.text(cx),
11283            "
11284                {{{a}}}
11285                {{{b}}}
11286                {{{c}}}
11287            "
11288            .unindent()
11289        );
11290        assert_eq!(
11291            display_ranges(editor, cx),
11292            [
11293                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
11294                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
11295                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
11296            ]
11297        );
11298
11299        editor.undo(&Undo, window, cx);
11300        editor.undo(&Undo, window, cx);
11301        editor.undo(&Undo, window, cx);
11302        assert_eq!(
11303            editor.text(cx),
11304            "
11305                a
11306                b
11307                c
11308            "
11309            .unindent()
11310        );
11311        assert_eq!(
11312            display_ranges(editor, cx),
11313            [
11314                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
11315                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
11316                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
11317            ]
11318        );
11319
11320        // Ensure inserting the first character of a multi-byte bracket pair
11321        // doesn't surround the selections with the bracket.
11322        editor.handle_input("/", window, cx);
11323        assert_eq!(
11324            editor.text(cx),
11325            "
11326                /
11327                /
11328                /
11329            "
11330            .unindent()
11331        );
11332        assert_eq!(
11333            display_ranges(editor, cx),
11334            [
11335                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
11336                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
11337                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
11338            ]
11339        );
11340
11341        editor.undo(&Undo, window, cx);
11342        assert_eq!(
11343            editor.text(cx),
11344            "
11345                a
11346                b
11347                c
11348            "
11349            .unindent()
11350        );
11351        assert_eq!(
11352            display_ranges(editor, cx),
11353            [
11354                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
11355                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
11356                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
11357            ]
11358        );
11359
11360        // Ensure inserting the last character of a multi-byte bracket pair
11361        // doesn't surround the selections with the bracket.
11362        editor.handle_input("*", window, cx);
11363        assert_eq!(
11364            editor.text(cx),
11365            "
11366                *
11367                *
11368                *
11369            "
11370            .unindent()
11371        );
11372        assert_eq!(
11373            display_ranges(editor, cx),
11374            [
11375                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
11376                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
11377                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
11378            ]
11379        );
11380    });
11381}
11382
11383#[gpui::test]
11384async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
11385    init_test(cx, |_| {});
11386
11387    let language = Arc::new(Language::new(
11388        LanguageConfig {
11389            brackets: BracketPairConfig {
11390                pairs: vec![BracketPair {
11391                    start: "{".to_string(),
11392                    end: "}".to_string(),
11393                    close: true,
11394                    surround: true,
11395                    newline: true,
11396                }],
11397                ..Default::default()
11398            },
11399            autoclose_before: "}".to_string(),
11400            ..Default::default()
11401        },
11402        Some(tree_sitter_rust::LANGUAGE.into()),
11403    ));
11404
11405    let text = r#"
11406        a
11407        b
11408        c
11409    "#
11410    .unindent();
11411
11412    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
11413    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11414    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11415    editor
11416        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
11417        .await;
11418
11419    editor.update_in(cx, |editor, window, cx| {
11420        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11421            s.select_ranges([
11422                Point::new(0, 1)..Point::new(0, 1),
11423                Point::new(1, 1)..Point::new(1, 1),
11424                Point::new(2, 1)..Point::new(2, 1),
11425            ])
11426        });
11427
11428        editor.handle_input("{", window, cx);
11429        editor.handle_input("{", window, cx);
11430        editor.handle_input("_", window, cx);
11431        assert_eq!(
11432            editor.text(cx),
11433            "
11434                a{{_}}
11435                b{{_}}
11436                c{{_}}
11437            "
11438            .unindent()
11439        );
11440        assert_eq!(
11441            editor
11442                .selections
11443                .ranges::<Point>(&editor.display_snapshot(cx)),
11444            [
11445                Point::new(0, 4)..Point::new(0, 4),
11446                Point::new(1, 4)..Point::new(1, 4),
11447                Point::new(2, 4)..Point::new(2, 4)
11448            ]
11449        );
11450
11451        editor.backspace(&Default::default(), window, cx);
11452        editor.backspace(&Default::default(), window, cx);
11453        assert_eq!(
11454            editor.text(cx),
11455            "
11456                a{}
11457                b{}
11458                c{}
11459            "
11460            .unindent()
11461        );
11462        assert_eq!(
11463            editor
11464                .selections
11465                .ranges::<Point>(&editor.display_snapshot(cx)),
11466            [
11467                Point::new(0, 2)..Point::new(0, 2),
11468                Point::new(1, 2)..Point::new(1, 2),
11469                Point::new(2, 2)..Point::new(2, 2)
11470            ]
11471        );
11472
11473        editor.delete_to_previous_word_start(&Default::default(), window, cx);
11474        assert_eq!(
11475            editor.text(cx),
11476            "
11477                a
11478                b
11479                c
11480            "
11481            .unindent()
11482        );
11483        assert_eq!(
11484            editor
11485                .selections
11486                .ranges::<Point>(&editor.display_snapshot(cx)),
11487            [
11488                Point::new(0, 1)..Point::new(0, 1),
11489                Point::new(1, 1)..Point::new(1, 1),
11490                Point::new(2, 1)..Point::new(2, 1)
11491            ]
11492        );
11493    });
11494}
11495
11496#[gpui::test]
11497async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
11498    init_test(cx, |settings| {
11499        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
11500    });
11501
11502    let mut cx = EditorTestContext::new(cx).await;
11503
11504    let language = Arc::new(Language::new(
11505        LanguageConfig {
11506            brackets: BracketPairConfig {
11507                pairs: vec![
11508                    BracketPair {
11509                        start: "{".to_string(),
11510                        end: "}".to_string(),
11511                        close: true,
11512                        surround: true,
11513                        newline: true,
11514                    },
11515                    BracketPair {
11516                        start: "(".to_string(),
11517                        end: ")".to_string(),
11518                        close: true,
11519                        surround: true,
11520                        newline: true,
11521                    },
11522                    BracketPair {
11523                        start: "[".to_string(),
11524                        end: "]".to_string(),
11525                        close: false,
11526                        surround: true,
11527                        newline: true,
11528                    },
11529                ],
11530                ..Default::default()
11531            },
11532            autoclose_before: "})]".to_string(),
11533            ..Default::default()
11534        },
11535        Some(tree_sitter_rust::LANGUAGE.into()),
11536    ));
11537
11538    cx.language_registry().add(language.clone());
11539    cx.update_buffer(|buffer, cx| {
11540        buffer.set_language(Some(language), cx);
11541    });
11542
11543    cx.set_state(
11544        &"
11545            {(ˇ)}
11546            [[ˇ]]
11547            {(ˇ)}
11548        "
11549        .unindent(),
11550    );
11551
11552    cx.update_editor(|editor, window, cx| {
11553        editor.backspace(&Default::default(), window, cx);
11554        editor.backspace(&Default::default(), window, cx);
11555    });
11556
11557    cx.assert_editor_state(
11558        &"
11559            ˇ
11560            ˇ]]
11561            ˇ
11562        "
11563        .unindent(),
11564    );
11565
11566    cx.update_editor(|editor, window, cx| {
11567        editor.handle_input("{", window, cx);
11568        editor.handle_input("{", window, cx);
11569        editor.move_right(&MoveRight, window, cx);
11570        editor.move_right(&MoveRight, window, cx);
11571        editor.move_left(&MoveLeft, window, cx);
11572        editor.move_left(&MoveLeft, window, cx);
11573        editor.backspace(&Default::default(), window, cx);
11574    });
11575
11576    cx.assert_editor_state(
11577        &"
11578            {ˇ}
11579            {ˇ}]]
11580            {ˇ}
11581        "
11582        .unindent(),
11583    );
11584
11585    cx.update_editor(|editor, window, cx| {
11586        editor.backspace(&Default::default(), window, cx);
11587    });
11588
11589    cx.assert_editor_state(
11590        &"
11591            ˇ
11592            ˇ]]
11593            ˇ
11594        "
11595        .unindent(),
11596    );
11597}
11598
11599#[gpui::test]
11600async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
11601    init_test(cx, |_| {});
11602
11603    let language = Arc::new(Language::new(
11604        LanguageConfig::default(),
11605        Some(tree_sitter_rust::LANGUAGE.into()),
11606    ));
11607
11608    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
11609    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11610    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11611    editor
11612        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
11613        .await;
11614
11615    editor.update_in(cx, |editor, window, cx| {
11616        editor.set_auto_replace_emoji_shortcode(true);
11617
11618        editor.handle_input("Hello ", window, cx);
11619        editor.handle_input(":wave", window, cx);
11620        assert_eq!(editor.text(cx), "Hello :wave".unindent());
11621
11622        editor.handle_input(":", window, cx);
11623        assert_eq!(editor.text(cx), "Hello 👋".unindent());
11624
11625        editor.handle_input(" :smile", window, cx);
11626        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
11627
11628        editor.handle_input(":", window, cx);
11629        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
11630
11631        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
11632        editor.handle_input(":wave", window, cx);
11633        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
11634
11635        editor.handle_input(":", window, cx);
11636        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
11637
11638        editor.handle_input(":1", window, cx);
11639        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
11640
11641        editor.handle_input(":", window, cx);
11642        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
11643
11644        // Ensure shortcode does not get replaced when it is part of a word
11645        editor.handle_input(" Test:wave", window, cx);
11646        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
11647
11648        editor.handle_input(":", window, cx);
11649        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
11650
11651        editor.set_auto_replace_emoji_shortcode(false);
11652
11653        // Ensure shortcode does not get replaced when auto replace is off
11654        editor.handle_input(" :wave", window, cx);
11655        assert_eq!(
11656            editor.text(cx),
11657            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
11658        );
11659
11660        editor.handle_input(":", window, cx);
11661        assert_eq!(
11662            editor.text(cx),
11663            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
11664        );
11665    });
11666}
11667
11668#[gpui::test]
11669async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
11670    init_test(cx, |_| {});
11671
11672    let (text, insertion_ranges) = marked_text_ranges(
11673        indoc! {"
11674            ˇ
11675        "},
11676        false,
11677    );
11678
11679    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
11680    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11681
11682    _ = editor.update_in(cx, |editor, window, cx| {
11683        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
11684
11685        editor
11686            .insert_snippet(
11687                &insertion_ranges
11688                    .iter()
11689                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
11690                    .collect::<Vec<_>>(),
11691                snippet,
11692                window,
11693                cx,
11694            )
11695            .unwrap();
11696
11697        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
11698            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
11699            assert_eq!(editor.text(cx), expected_text);
11700            assert_eq!(
11701                editor.selections.ranges(&editor.display_snapshot(cx)),
11702                selection_ranges
11703                    .iter()
11704                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
11705                    .collect::<Vec<_>>()
11706            );
11707        }
11708
11709        assert(
11710            editor,
11711            cx,
11712            indoc! {"
11713            type «» =•
11714            "},
11715        );
11716
11717        assert!(editor.context_menu_visible(), "There should be a matches");
11718    });
11719}
11720
11721#[gpui::test]
11722async fn test_snippet_tabstop_navigation_with_placeholders(cx: &mut TestAppContext) {
11723    init_test(cx, |_| {});
11724
11725    fn assert_state(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
11726        let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
11727        assert_eq!(editor.text(cx), expected_text);
11728        assert_eq!(
11729            editor.selections.ranges(&editor.display_snapshot(cx)),
11730            selection_ranges
11731                .iter()
11732                .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
11733                .collect::<Vec<_>>()
11734        );
11735    }
11736
11737    let (text, insertion_ranges) = marked_text_ranges(
11738        indoc! {"
11739            ˇ
11740        "},
11741        false,
11742    );
11743
11744    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
11745    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11746
11747    _ = editor.update_in(cx, |editor, window, cx| {
11748        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2; $3").unwrap();
11749
11750        editor
11751            .insert_snippet(
11752                &insertion_ranges
11753                    .iter()
11754                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
11755                    .collect::<Vec<_>>(),
11756                snippet,
11757                window,
11758                cx,
11759            )
11760            .unwrap();
11761
11762        assert_state(
11763            editor,
11764            cx,
11765            indoc! {"
11766            type «» = ;•
11767            "},
11768        );
11769
11770        assert!(
11771            editor.context_menu_visible(),
11772            "Context menu should be visible for placeholder choices"
11773        );
11774
11775        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11776
11777        assert_state(
11778            editor,
11779            cx,
11780            indoc! {"
11781            type  = «»;•
11782            "},
11783        );
11784
11785        assert!(
11786            !editor.context_menu_visible(),
11787            "Context menu should be hidden after moving to next tabstop"
11788        );
11789
11790        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11791
11792        assert_state(
11793            editor,
11794            cx,
11795            indoc! {"
11796            type  = ; ˇ
11797            "},
11798        );
11799
11800        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11801
11802        assert_state(
11803            editor,
11804            cx,
11805            indoc! {"
11806            type  = ; ˇ
11807            "},
11808        );
11809    });
11810
11811    _ = editor.update_in(cx, |editor, window, cx| {
11812        editor.select_all(&SelectAll, window, cx);
11813        editor.backspace(&Backspace, window, cx);
11814
11815        let snippet = Snippet::parse("fn ${1|,foo,bar|} = ${2:value}; $3").unwrap();
11816        let insertion_ranges = editor
11817            .selections
11818            .all(&editor.display_snapshot(cx))
11819            .iter()
11820            .map(|s| s.range())
11821            .collect::<Vec<_>>();
11822
11823        editor
11824            .insert_snippet(&insertion_ranges, snippet, window, cx)
11825            .unwrap();
11826
11827        assert_state(editor, cx, "fn «» = value;•");
11828
11829        assert!(
11830            editor.context_menu_visible(),
11831            "Context menu should be visible for placeholder choices"
11832        );
11833
11834        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11835
11836        assert_state(editor, cx, "fn  = «valueˇ»;•");
11837
11838        editor.previous_snippet_tabstop(&PreviousSnippetTabstop, window, cx);
11839
11840        assert_state(editor, cx, "fn «» = value;•");
11841
11842        assert!(
11843            editor.context_menu_visible(),
11844            "Context menu should be visible again after returning to first tabstop"
11845        );
11846
11847        editor.previous_snippet_tabstop(&PreviousSnippetTabstop, window, cx);
11848
11849        assert_state(editor, cx, "fn «» = value;•");
11850    });
11851}
11852
11853#[gpui::test]
11854async fn test_snippets(cx: &mut TestAppContext) {
11855    init_test(cx, |_| {});
11856
11857    let mut cx = EditorTestContext::new(cx).await;
11858
11859    cx.set_state(indoc! {"
11860        a.ˇ b
11861        a.ˇ b
11862        a.ˇ b
11863    "});
11864
11865    cx.update_editor(|editor, window, cx| {
11866        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
11867        let insertion_ranges = editor
11868            .selections
11869            .all(&editor.display_snapshot(cx))
11870            .iter()
11871            .map(|s| s.range())
11872            .collect::<Vec<_>>();
11873        editor
11874            .insert_snippet(&insertion_ranges, snippet, window, cx)
11875            .unwrap();
11876    });
11877
11878    cx.assert_editor_state(indoc! {"
11879        a.f(«oneˇ», two, «threeˇ») b
11880        a.f(«oneˇ», two, «threeˇ») b
11881        a.f(«oneˇ», two, «threeˇ») b
11882    "});
11883
11884    // Can't move earlier than the first tab stop
11885    cx.update_editor(|editor, window, cx| {
11886        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
11887    });
11888    cx.assert_editor_state(indoc! {"
11889        a.f(«oneˇ», two, «threeˇ») b
11890        a.f(«oneˇ», two, «threeˇ») b
11891        a.f(«oneˇ», two, «threeˇ») b
11892    "});
11893
11894    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11895    cx.assert_editor_state(indoc! {"
11896        a.f(one, «twoˇ», three) b
11897        a.f(one, «twoˇ», three) b
11898        a.f(one, «twoˇ», three) b
11899    "});
11900
11901    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
11902    cx.assert_editor_state(indoc! {"
11903        a.f(«oneˇ», two, «threeˇ») b
11904        a.f(«oneˇ», two, «threeˇ») b
11905        a.f(«oneˇ», two, «threeˇ») b
11906    "});
11907
11908    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11909    cx.assert_editor_state(indoc! {"
11910        a.f(one, «twoˇ», three) b
11911        a.f(one, «twoˇ», three) b
11912        a.f(one, «twoˇ», three) b
11913    "});
11914    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11915    cx.assert_editor_state(indoc! {"
11916        a.f(one, two, three)ˇ b
11917        a.f(one, two, three)ˇ b
11918        a.f(one, two, three)ˇ b
11919    "});
11920
11921    // As soon as the last tab stop is reached, snippet state is gone
11922    cx.update_editor(|editor, window, cx| {
11923        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
11924    });
11925    cx.assert_editor_state(indoc! {"
11926        a.f(one, two, three)ˇ b
11927        a.f(one, two, three)ˇ b
11928        a.f(one, two, three)ˇ b
11929    "});
11930}
11931
11932#[gpui::test]
11933async fn test_snippet_indentation(cx: &mut TestAppContext) {
11934    init_test(cx, |_| {});
11935
11936    let mut cx = EditorTestContext::new(cx).await;
11937
11938    cx.update_editor(|editor, window, cx| {
11939        let snippet = Snippet::parse(indoc! {"
11940            /*
11941             * Multiline comment with leading indentation
11942             *
11943             * $1
11944             */
11945            $0"})
11946        .unwrap();
11947        let insertion_ranges = editor
11948            .selections
11949            .all(&editor.display_snapshot(cx))
11950            .iter()
11951            .map(|s| s.range())
11952            .collect::<Vec<_>>();
11953        editor
11954            .insert_snippet(&insertion_ranges, snippet, window, cx)
11955            .unwrap();
11956    });
11957
11958    cx.assert_editor_state(indoc! {"
11959        /*
11960         * Multiline comment with leading indentation
11961         *
11962         * ˇ
11963         */
11964    "});
11965
11966    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11967    cx.assert_editor_state(indoc! {"
11968        /*
11969         * Multiline comment with leading indentation
11970         *
11971         *•
11972         */
11973        ˇ"});
11974}
11975
11976#[gpui::test]
11977async fn test_snippet_with_multi_word_prefix(cx: &mut TestAppContext) {
11978    init_test(cx, |_| {});
11979
11980    let mut cx = EditorTestContext::new(cx).await;
11981    cx.update_editor(|editor, _, cx| {
11982        editor.project().unwrap().update(cx, |project, cx| {
11983            project.snippets().update(cx, |snippets, _cx| {
11984                let snippet = project::snippet_provider::Snippet {
11985                    prefix: vec!["multi word".to_string()],
11986                    body: "this is many words".to_string(),
11987                    description: Some("description".to_string()),
11988                    name: "multi-word snippet test".to_string(),
11989                };
11990                snippets.add_snippet_for_test(
11991                    None,
11992                    PathBuf::from("test_snippets.json"),
11993                    vec![Arc::new(snippet)],
11994                );
11995            });
11996        })
11997    });
11998
11999    for (input_to_simulate, should_match_snippet) in [
12000        ("m", true),
12001        ("m ", true),
12002        ("m w", true),
12003        ("aa m w", true),
12004        ("aa m g", false),
12005    ] {
12006        cx.set_state("ˇ");
12007        cx.simulate_input(input_to_simulate); // fails correctly
12008
12009        cx.update_editor(|editor, _, _| {
12010            let Some(CodeContextMenu::Completions(context_menu)) = &*editor.context_menu.borrow()
12011            else {
12012                assert!(!should_match_snippet); // no completions! don't even show the menu
12013                return;
12014            };
12015            assert!(context_menu.visible());
12016            let completions = context_menu.completions.borrow();
12017
12018            assert_eq!(!completions.is_empty(), should_match_snippet);
12019        });
12020    }
12021}
12022
12023#[gpui::test]
12024async fn test_document_format_during_save(cx: &mut TestAppContext) {
12025    init_test(cx, |_| {});
12026
12027    let fs = FakeFs::new(cx.executor());
12028    fs.insert_file(path!("/file.rs"), Default::default()).await;
12029
12030    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
12031
12032    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12033    language_registry.add(rust_lang());
12034    let mut fake_servers = language_registry.register_fake_lsp(
12035        "Rust",
12036        FakeLspAdapter {
12037            capabilities: lsp::ServerCapabilities {
12038                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12039                ..Default::default()
12040            },
12041            ..Default::default()
12042        },
12043    );
12044
12045    let buffer = project
12046        .update(cx, |project, cx| {
12047            project.open_local_buffer(path!("/file.rs"), cx)
12048        })
12049        .await
12050        .unwrap();
12051
12052    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12053    let (editor, cx) = cx.add_window_view(|window, cx| {
12054        build_editor_with_project(project.clone(), buffer, window, cx)
12055    });
12056    editor.update_in(cx, |editor, window, cx| {
12057        editor.set_text("one\ntwo\nthree\n", window, cx)
12058    });
12059    assert!(cx.read(|cx| editor.is_dirty(cx)));
12060
12061    let fake_server = fake_servers.next().await.unwrap();
12062
12063    {
12064        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12065            move |params, _| async move {
12066                assert_eq!(
12067                    params.text_document.uri,
12068                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12069                );
12070                assert_eq!(params.options.tab_size, 4);
12071                Ok(Some(vec![lsp::TextEdit::new(
12072                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
12073                    ", ".to_string(),
12074                )]))
12075            },
12076        );
12077        let save = editor
12078            .update_in(cx, |editor, window, cx| {
12079                editor.save(
12080                    SaveOptions {
12081                        format: true,
12082                        autosave: false,
12083                    },
12084                    project.clone(),
12085                    window,
12086                    cx,
12087                )
12088            })
12089            .unwrap();
12090        save.await;
12091
12092        assert_eq!(
12093            editor.update(cx, |editor, cx| editor.text(cx)),
12094            "one, two\nthree\n"
12095        );
12096        assert!(!cx.read(|cx| editor.is_dirty(cx)));
12097    }
12098
12099    {
12100        editor.update_in(cx, |editor, window, cx| {
12101            editor.set_text("one\ntwo\nthree\n", window, cx)
12102        });
12103        assert!(cx.read(|cx| editor.is_dirty(cx)));
12104
12105        // Ensure we can still save even if formatting hangs.
12106        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12107            move |params, _| async move {
12108                assert_eq!(
12109                    params.text_document.uri,
12110                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12111                );
12112                futures::future::pending::<()>().await;
12113                unreachable!()
12114            },
12115        );
12116        let save = editor
12117            .update_in(cx, |editor, window, cx| {
12118                editor.save(
12119                    SaveOptions {
12120                        format: true,
12121                        autosave: false,
12122                    },
12123                    project.clone(),
12124                    window,
12125                    cx,
12126                )
12127            })
12128            .unwrap();
12129        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
12130        save.await;
12131        assert_eq!(
12132            editor.update(cx, |editor, cx| editor.text(cx)),
12133            "one\ntwo\nthree\n"
12134        );
12135    }
12136
12137    // Set rust language override and assert overridden tabsize is sent to language server
12138    update_test_language_settings(cx, |settings| {
12139        settings.languages.0.insert(
12140            "Rust".into(),
12141            LanguageSettingsContent {
12142                tab_size: NonZeroU32::new(8),
12143                ..Default::default()
12144            },
12145        );
12146    });
12147
12148    {
12149        editor.update_in(cx, |editor, window, cx| {
12150            editor.set_text("somehting_new\n", window, cx)
12151        });
12152        assert!(cx.read(|cx| editor.is_dirty(cx)));
12153        let _formatting_request_signal = fake_server
12154            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
12155                assert_eq!(
12156                    params.text_document.uri,
12157                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12158                );
12159                assert_eq!(params.options.tab_size, 8);
12160                Ok(Some(vec![]))
12161            });
12162        let save = editor
12163            .update_in(cx, |editor, window, cx| {
12164                editor.save(
12165                    SaveOptions {
12166                        format: true,
12167                        autosave: false,
12168                    },
12169                    project.clone(),
12170                    window,
12171                    cx,
12172                )
12173            })
12174            .unwrap();
12175        save.await;
12176    }
12177}
12178
12179#[gpui::test]
12180async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
12181    init_test(cx, |settings| {
12182        settings.defaults.ensure_final_newline_on_save = Some(false);
12183    });
12184
12185    let fs = FakeFs::new(cx.executor());
12186    fs.insert_file(path!("/file.txt"), "foo".into()).await;
12187
12188    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
12189
12190    let buffer = project
12191        .update(cx, |project, cx| {
12192            project.open_local_buffer(path!("/file.txt"), cx)
12193        })
12194        .await
12195        .unwrap();
12196
12197    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12198    let (editor, cx) = cx.add_window_view(|window, cx| {
12199        build_editor_with_project(project.clone(), buffer, window, cx)
12200    });
12201    editor.update_in(cx, |editor, window, cx| {
12202        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
12203            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
12204        });
12205    });
12206    assert!(!cx.read(|cx| editor.is_dirty(cx)));
12207
12208    editor.update_in(cx, |editor, window, cx| {
12209        editor.handle_input("\n", window, cx)
12210    });
12211    cx.run_until_parked();
12212    save(&editor, &project, cx).await;
12213    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
12214
12215    editor.update_in(cx, |editor, window, cx| {
12216        editor.undo(&Default::default(), window, cx);
12217    });
12218    save(&editor, &project, cx).await;
12219    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
12220
12221    editor.update_in(cx, |editor, window, cx| {
12222        editor.redo(&Default::default(), window, cx);
12223    });
12224    cx.run_until_parked();
12225    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
12226
12227    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
12228        let save = editor
12229            .update_in(cx, |editor, window, cx| {
12230                editor.save(
12231                    SaveOptions {
12232                        format: true,
12233                        autosave: false,
12234                    },
12235                    project.clone(),
12236                    window,
12237                    cx,
12238                )
12239            })
12240            .unwrap();
12241        save.await;
12242        assert!(!cx.read(|cx| editor.is_dirty(cx)));
12243    }
12244}
12245
12246#[gpui::test]
12247async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
12248    init_test(cx, |_| {});
12249
12250    let cols = 4;
12251    let rows = 10;
12252    let sample_text_1 = sample_text(rows, cols, 'a');
12253    assert_eq!(
12254        sample_text_1,
12255        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
12256    );
12257    let sample_text_2 = sample_text(rows, cols, 'l');
12258    assert_eq!(
12259        sample_text_2,
12260        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
12261    );
12262    let sample_text_3 = sample_text(rows, cols, 'v');
12263    assert_eq!(
12264        sample_text_3,
12265        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
12266    );
12267
12268    let fs = FakeFs::new(cx.executor());
12269    fs.insert_tree(
12270        path!("/a"),
12271        json!({
12272            "main.rs": sample_text_1,
12273            "other.rs": sample_text_2,
12274            "lib.rs": sample_text_3,
12275        }),
12276    )
12277    .await;
12278
12279    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
12280    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12281    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
12282
12283    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12284    language_registry.add(rust_lang());
12285    let mut fake_servers = language_registry.register_fake_lsp(
12286        "Rust",
12287        FakeLspAdapter {
12288            capabilities: lsp::ServerCapabilities {
12289                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12290                ..Default::default()
12291            },
12292            ..Default::default()
12293        },
12294    );
12295
12296    let worktree = project.update(cx, |project, cx| {
12297        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
12298        assert_eq!(worktrees.len(), 1);
12299        worktrees.pop().unwrap()
12300    });
12301    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
12302
12303    let buffer_1 = project
12304        .update(cx, |project, cx| {
12305            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
12306        })
12307        .await
12308        .unwrap();
12309    let buffer_2 = project
12310        .update(cx, |project, cx| {
12311            project.open_buffer((worktree_id, rel_path("other.rs")), cx)
12312        })
12313        .await
12314        .unwrap();
12315    let buffer_3 = project
12316        .update(cx, |project, cx| {
12317            project.open_buffer((worktree_id, rel_path("lib.rs")), cx)
12318        })
12319        .await
12320        .unwrap();
12321
12322    let multi_buffer = cx.new(|cx| {
12323        let mut multi_buffer = MultiBuffer::new(ReadWrite);
12324        multi_buffer.push_excerpts(
12325            buffer_1.clone(),
12326            [
12327                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
12328                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
12329                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
12330            ],
12331            cx,
12332        );
12333        multi_buffer.push_excerpts(
12334            buffer_2.clone(),
12335            [
12336                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
12337                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
12338                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
12339            ],
12340            cx,
12341        );
12342        multi_buffer.push_excerpts(
12343            buffer_3.clone(),
12344            [
12345                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
12346                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
12347                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
12348            ],
12349            cx,
12350        );
12351        multi_buffer
12352    });
12353    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
12354        Editor::new(
12355            EditorMode::full(),
12356            multi_buffer,
12357            Some(project.clone()),
12358            window,
12359            cx,
12360        )
12361    });
12362
12363    multi_buffer_editor.update_in(cx, |editor, window, cx| {
12364        editor.change_selections(
12365            SelectionEffects::scroll(Autoscroll::Next),
12366            window,
12367            cx,
12368            |s| s.select_ranges(Some(MultiBufferOffset(1)..MultiBufferOffset(2))),
12369        );
12370        editor.insert("|one|two|three|", window, cx);
12371    });
12372    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
12373    multi_buffer_editor.update_in(cx, |editor, window, cx| {
12374        editor.change_selections(
12375            SelectionEffects::scroll(Autoscroll::Next),
12376            window,
12377            cx,
12378            |s| s.select_ranges(Some(MultiBufferOffset(60)..MultiBufferOffset(70))),
12379        );
12380        editor.insert("|four|five|six|", window, cx);
12381    });
12382    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
12383
12384    // First two buffers should be edited, but not the third one.
12385    assert_eq!(
12386        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
12387        "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}",
12388    );
12389    buffer_1.update(cx, |buffer, _| {
12390        assert!(buffer.is_dirty());
12391        assert_eq!(
12392            buffer.text(),
12393            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
12394        )
12395    });
12396    buffer_2.update(cx, |buffer, _| {
12397        assert!(buffer.is_dirty());
12398        assert_eq!(
12399            buffer.text(),
12400            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
12401        )
12402    });
12403    buffer_3.update(cx, |buffer, _| {
12404        assert!(!buffer.is_dirty());
12405        assert_eq!(buffer.text(), sample_text_3,)
12406    });
12407    cx.executor().run_until_parked();
12408
12409    let save = multi_buffer_editor
12410        .update_in(cx, |editor, window, cx| {
12411            editor.save(
12412                SaveOptions {
12413                    format: true,
12414                    autosave: false,
12415                },
12416                project.clone(),
12417                window,
12418                cx,
12419            )
12420        })
12421        .unwrap();
12422
12423    let fake_server = fake_servers.next().await.unwrap();
12424    fake_server
12425        .server
12426        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
12427            Ok(Some(vec![lsp::TextEdit::new(
12428                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
12429                format!("[{} formatted]", params.text_document.uri),
12430            )]))
12431        })
12432        .detach();
12433    save.await;
12434
12435    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
12436    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
12437    assert_eq!(
12438        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
12439        uri!(
12440            "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}"
12441        ),
12442    );
12443    buffer_1.update(cx, |buffer, _| {
12444        assert!(!buffer.is_dirty());
12445        assert_eq!(
12446            buffer.text(),
12447            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
12448        )
12449    });
12450    buffer_2.update(cx, |buffer, _| {
12451        assert!(!buffer.is_dirty());
12452        assert_eq!(
12453            buffer.text(),
12454            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
12455        )
12456    });
12457    buffer_3.update(cx, |buffer, _| {
12458        assert!(!buffer.is_dirty());
12459        assert_eq!(buffer.text(), sample_text_3,)
12460    });
12461}
12462
12463#[gpui::test]
12464async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
12465    init_test(cx, |_| {});
12466
12467    let fs = FakeFs::new(cx.executor());
12468    fs.insert_tree(
12469        path!("/dir"),
12470        json!({
12471            "file1.rs": "fn main() { println!(\"hello\"); }",
12472            "file2.rs": "fn test() { println!(\"test\"); }",
12473            "file3.rs": "fn other() { println!(\"other\"); }\n",
12474        }),
12475    )
12476    .await;
12477
12478    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
12479    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12480    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
12481
12482    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12483    language_registry.add(rust_lang());
12484
12485    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
12486    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
12487
12488    // Open three buffers
12489    let buffer_1 = project
12490        .update(cx, |project, cx| {
12491            project.open_buffer((worktree_id, rel_path("file1.rs")), cx)
12492        })
12493        .await
12494        .unwrap();
12495    let buffer_2 = project
12496        .update(cx, |project, cx| {
12497            project.open_buffer((worktree_id, rel_path("file2.rs")), cx)
12498        })
12499        .await
12500        .unwrap();
12501    let buffer_3 = project
12502        .update(cx, |project, cx| {
12503            project.open_buffer((worktree_id, rel_path("file3.rs")), cx)
12504        })
12505        .await
12506        .unwrap();
12507
12508    // Create a multi-buffer with all three buffers
12509    let multi_buffer = cx.new(|cx| {
12510        let mut multi_buffer = MultiBuffer::new(ReadWrite);
12511        multi_buffer.push_excerpts(
12512            buffer_1.clone(),
12513            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
12514            cx,
12515        );
12516        multi_buffer.push_excerpts(
12517            buffer_2.clone(),
12518            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
12519            cx,
12520        );
12521        multi_buffer.push_excerpts(
12522            buffer_3.clone(),
12523            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
12524            cx,
12525        );
12526        multi_buffer
12527    });
12528
12529    let editor = cx.new_window_entity(|window, cx| {
12530        Editor::new(
12531            EditorMode::full(),
12532            multi_buffer,
12533            Some(project.clone()),
12534            window,
12535            cx,
12536        )
12537    });
12538
12539    // Edit only the first buffer
12540    editor.update_in(cx, |editor, window, cx| {
12541        editor.change_selections(
12542            SelectionEffects::scroll(Autoscroll::Next),
12543            window,
12544            cx,
12545            |s| s.select_ranges(Some(MultiBufferOffset(10)..MultiBufferOffset(10))),
12546        );
12547        editor.insert("// edited", window, cx);
12548    });
12549
12550    // Verify that only buffer 1 is dirty
12551    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
12552    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12553    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12554
12555    // Get write counts after file creation (files were created with initial content)
12556    // We expect each file to have been written once during creation
12557    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
12558    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
12559    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
12560
12561    // Perform autosave
12562    let save_task = editor.update_in(cx, |editor, window, cx| {
12563        editor.save(
12564            SaveOptions {
12565                format: true,
12566                autosave: true,
12567            },
12568            project.clone(),
12569            window,
12570            cx,
12571        )
12572    });
12573    save_task.await.unwrap();
12574
12575    // Only the dirty buffer should have been saved
12576    assert_eq!(
12577        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
12578        1,
12579        "Buffer 1 was dirty, so it should have been written once during autosave"
12580    );
12581    assert_eq!(
12582        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
12583        0,
12584        "Buffer 2 was clean, so it should not have been written during autosave"
12585    );
12586    assert_eq!(
12587        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
12588        0,
12589        "Buffer 3 was clean, so it should not have been written during autosave"
12590    );
12591
12592    // Verify buffer states after autosave
12593    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12594    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12595    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12596
12597    // Now perform a manual save (format = true)
12598    let save_task = editor.update_in(cx, |editor, window, cx| {
12599        editor.save(
12600            SaveOptions {
12601                format: true,
12602                autosave: false,
12603            },
12604            project.clone(),
12605            window,
12606            cx,
12607        )
12608    });
12609    save_task.await.unwrap();
12610
12611    // During manual save, clean buffers don't get written to disk
12612    // They just get did_save called for language server notifications
12613    assert_eq!(
12614        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
12615        1,
12616        "Buffer 1 should only have been written once total (during autosave, not manual save)"
12617    );
12618    assert_eq!(
12619        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
12620        0,
12621        "Buffer 2 should not have been written at all"
12622    );
12623    assert_eq!(
12624        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
12625        0,
12626        "Buffer 3 should not have been written at all"
12627    );
12628}
12629
12630async fn setup_range_format_test(
12631    cx: &mut TestAppContext,
12632) -> (
12633    Entity<Project>,
12634    Entity<Editor>,
12635    &mut gpui::VisualTestContext,
12636    lsp::FakeLanguageServer,
12637) {
12638    init_test(cx, |_| {});
12639
12640    let fs = FakeFs::new(cx.executor());
12641    fs.insert_file(path!("/file.rs"), Default::default()).await;
12642
12643    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12644
12645    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12646    language_registry.add(rust_lang());
12647    let mut fake_servers = language_registry.register_fake_lsp(
12648        "Rust",
12649        FakeLspAdapter {
12650            capabilities: lsp::ServerCapabilities {
12651                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
12652                ..lsp::ServerCapabilities::default()
12653            },
12654            ..FakeLspAdapter::default()
12655        },
12656    );
12657
12658    let buffer = project
12659        .update(cx, |project, cx| {
12660            project.open_local_buffer(path!("/file.rs"), cx)
12661        })
12662        .await
12663        .unwrap();
12664
12665    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12666    let (editor, cx) = cx.add_window_view(|window, cx| {
12667        build_editor_with_project(project.clone(), buffer, window, cx)
12668    });
12669
12670    let fake_server = fake_servers.next().await.unwrap();
12671
12672    (project, editor, cx, fake_server)
12673}
12674
12675#[gpui::test]
12676async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
12677    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12678
12679    editor.update_in(cx, |editor, window, cx| {
12680        editor.set_text("one\ntwo\nthree\n", window, cx)
12681    });
12682    assert!(cx.read(|cx| editor.is_dirty(cx)));
12683
12684    let save = editor
12685        .update_in(cx, |editor, window, cx| {
12686            editor.save(
12687                SaveOptions {
12688                    format: true,
12689                    autosave: false,
12690                },
12691                project.clone(),
12692                window,
12693                cx,
12694            )
12695        })
12696        .unwrap();
12697    fake_server
12698        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
12699            assert_eq!(
12700                params.text_document.uri,
12701                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12702            );
12703            assert_eq!(params.options.tab_size, 4);
12704            Ok(Some(vec![lsp::TextEdit::new(
12705                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
12706                ", ".to_string(),
12707            )]))
12708        })
12709        .next()
12710        .await;
12711    save.await;
12712    assert_eq!(
12713        editor.update(cx, |editor, cx| editor.text(cx)),
12714        "one, two\nthree\n"
12715    );
12716    assert!(!cx.read(|cx| editor.is_dirty(cx)));
12717}
12718
12719#[gpui::test]
12720async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
12721    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12722
12723    editor.update_in(cx, |editor, window, cx| {
12724        editor.set_text("one\ntwo\nthree\n", window, cx)
12725    });
12726    assert!(cx.read(|cx| editor.is_dirty(cx)));
12727
12728    // Test that save still works when formatting hangs
12729    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
12730        move |params, _| async move {
12731            assert_eq!(
12732                params.text_document.uri,
12733                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12734            );
12735            futures::future::pending::<()>().await;
12736            unreachable!()
12737        },
12738    );
12739    let save = editor
12740        .update_in(cx, |editor, window, cx| {
12741            editor.save(
12742                SaveOptions {
12743                    format: true,
12744                    autosave: false,
12745                },
12746                project.clone(),
12747                window,
12748                cx,
12749            )
12750        })
12751        .unwrap();
12752    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
12753    save.await;
12754    assert_eq!(
12755        editor.update(cx, |editor, cx| editor.text(cx)),
12756        "one\ntwo\nthree\n"
12757    );
12758    assert!(!cx.read(|cx| editor.is_dirty(cx)));
12759}
12760
12761#[gpui::test]
12762async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
12763    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12764
12765    // Buffer starts clean, no formatting should be requested
12766    let save = editor
12767        .update_in(cx, |editor, window, cx| {
12768            editor.save(
12769                SaveOptions {
12770                    format: false,
12771                    autosave: false,
12772                },
12773                project.clone(),
12774                window,
12775                cx,
12776            )
12777        })
12778        .unwrap();
12779    let _pending_format_request = fake_server
12780        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
12781            panic!("Should not be invoked");
12782        })
12783        .next();
12784    save.await;
12785    cx.run_until_parked();
12786}
12787
12788#[gpui::test]
12789async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
12790    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12791
12792    // Set Rust language override and assert overridden tabsize is sent to language server
12793    update_test_language_settings(cx, |settings| {
12794        settings.languages.0.insert(
12795            "Rust".into(),
12796            LanguageSettingsContent {
12797                tab_size: NonZeroU32::new(8),
12798                ..Default::default()
12799            },
12800        );
12801    });
12802
12803    editor.update_in(cx, |editor, window, cx| {
12804        editor.set_text("something_new\n", window, cx)
12805    });
12806    assert!(cx.read(|cx| editor.is_dirty(cx)));
12807    let save = editor
12808        .update_in(cx, |editor, window, cx| {
12809            editor.save(
12810                SaveOptions {
12811                    format: true,
12812                    autosave: false,
12813                },
12814                project.clone(),
12815                window,
12816                cx,
12817            )
12818        })
12819        .unwrap();
12820    fake_server
12821        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
12822            assert_eq!(
12823                params.text_document.uri,
12824                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12825            );
12826            assert_eq!(params.options.tab_size, 8);
12827            Ok(Some(Vec::new()))
12828        })
12829        .next()
12830        .await;
12831    save.await;
12832}
12833
12834#[gpui::test]
12835async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
12836    init_test(cx, |settings| {
12837        settings.defaults.formatter = Some(FormatterList::Single(Formatter::LanguageServer(
12838            settings::LanguageServerFormatterSpecifier::Current,
12839        )))
12840    });
12841
12842    let fs = FakeFs::new(cx.executor());
12843    fs.insert_file(path!("/file.rs"), Default::default()).await;
12844
12845    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12846
12847    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12848    language_registry.add(Arc::new(Language::new(
12849        LanguageConfig {
12850            name: "Rust".into(),
12851            matcher: LanguageMatcher {
12852                path_suffixes: vec!["rs".to_string()],
12853                ..Default::default()
12854            },
12855            ..LanguageConfig::default()
12856        },
12857        Some(tree_sitter_rust::LANGUAGE.into()),
12858    )));
12859    update_test_language_settings(cx, |settings| {
12860        // Enable Prettier formatting for the same buffer, and ensure
12861        // LSP is called instead of Prettier.
12862        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
12863    });
12864    let mut fake_servers = language_registry.register_fake_lsp(
12865        "Rust",
12866        FakeLspAdapter {
12867            capabilities: lsp::ServerCapabilities {
12868                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12869                ..Default::default()
12870            },
12871            ..Default::default()
12872        },
12873    );
12874
12875    let buffer = project
12876        .update(cx, |project, cx| {
12877            project.open_local_buffer(path!("/file.rs"), cx)
12878        })
12879        .await
12880        .unwrap();
12881
12882    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12883    let (editor, cx) = cx.add_window_view(|window, cx| {
12884        build_editor_with_project(project.clone(), buffer, window, cx)
12885    });
12886    editor.update_in(cx, |editor, window, cx| {
12887        editor.set_text("one\ntwo\nthree\n", window, cx)
12888    });
12889
12890    let fake_server = fake_servers.next().await.unwrap();
12891
12892    let format = editor
12893        .update_in(cx, |editor, window, cx| {
12894            editor.perform_format(
12895                project.clone(),
12896                FormatTrigger::Manual,
12897                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12898                window,
12899                cx,
12900            )
12901        })
12902        .unwrap();
12903    fake_server
12904        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
12905            assert_eq!(
12906                params.text_document.uri,
12907                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12908            );
12909            assert_eq!(params.options.tab_size, 4);
12910            Ok(Some(vec![lsp::TextEdit::new(
12911                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
12912                ", ".to_string(),
12913            )]))
12914        })
12915        .next()
12916        .await;
12917    format.await;
12918    assert_eq!(
12919        editor.update(cx, |editor, cx| editor.text(cx)),
12920        "one, two\nthree\n"
12921    );
12922
12923    editor.update_in(cx, |editor, window, cx| {
12924        editor.set_text("one\ntwo\nthree\n", window, cx)
12925    });
12926    // Ensure we don't lock if formatting hangs.
12927    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12928        move |params, _| async move {
12929            assert_eq!(
12930                params.text_document.uri,
12931                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12932            );
12933            futures::future::pending::<()>().await;
12934            unreachable!()
12935        },
12936    );
12937    let format = editor
12938        .update_in(cx, |editor, window, cx| {
12939            editor.perform_format(
12940                project,
12941                FormatTrigger::Manual,
12942                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12943                window,
12944                cx,
12945            )
12946        })
12947        .unwrap();
12948    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
12949    format.await;
12950    assert_eq!(
12951        editor.update(cx, |editor, cx| editor.text(cx)),
12952        "one\ntwo\nthree\n"
12953    );
12954}
12955
12956#[gpui::test]
12957async fn test_multiple_formatters(cx: &mut TestAppContext) {
12958    init_test(cx, |settings| {
12959        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
12960        settings.defaults.formatter = Some(FormatterList::Vec(vec![
12961            Formatter::LanguageServer(settings::LanguageServerFormatterSpecifier::Current),
12962            Formatter::CodeAction("code-action-1".into()),
12963            Formatter::CodeAction("code-action-2".into()),
12964        ]))
12965    });
12966
12967    let fs = FakeFs::new(cx.executor());
12968    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
12969        .await;
12970
12971    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12972    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12973    language_registry.add(rust_lang());
12974
12975    let mut fake_servers = language_registry.register_fake_lsp(
12976        "Rust",
12977        FakeLspAdapter {
12978            capabilities: lsp::ServerCapabilities {
12979                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12980                execute_command_provider: Some(lsp::ExecuteCommandOptions {
12981                    commands: vec!["the-command-for-code-action-1".into()],
12982                    ..Default::default()
12983                }),
12984                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
12985                ..Default::default()
12986            },
12987            ..Default::default()
12988        },
12989    );
12990
12991    let buffer = project
12992        .update(cx, |project, cx| {
12993            project.open_local_buffer(path!("/file.rs"), cx)
12994        })
12995        .await
12996        .unwrap();
12997
12998    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12999    let (editor, cx) = cx.add_window_view(|window, cx| {
13000        build_editor_with_project(project.clone(), buffer, window, cx)
13001    });
13002
13003    let fake_server = fake_servers.next().await.unwrap();
13004    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
13005        move |_params, _| async move {
13006            Ok(Some(vec![lsp::TextEdit::new(
13007                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
13008                "applied-formatting\n".to_string(),
13009            )]))
13010        },
13011    );
13012    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
13013        move |params, _| async move {
13014            let requested_code_actions = params.context.only.expect("Expected code action request");
13015            assert_eq!(requested_code_actions.len(), 1);
13016
13017            let uri = lsp::Uri::from_file_path(path!("/file.rs")).unwrap();
13018            let code_action = match requested_code_actions[0].as_str() {
13019                "code-action-1" => lsp::CodeAction {
13020                    kind: Some("code-action-1".into()),
13021                    edit: Some(lsp::WorkspaceEdit::new(
13022                        [(
13023                            uri,
13024                            vec![lsp::TextEdit::new(
13025                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
13026                                "applied-code-action-1-edit\n".to_string(),
13027                            )],
13028                        )]
13029                        .into_iter()
13030                        .collect(),
13031                    )),
13032                    command: Some(lsp::Command {
13033                        command: "the-command-for-code-action-1".into(),
13034                        ..Default::default()
13035                    }),
13036                    ..Default::default()
13037                },
13038                "code-action-2" => lsp::CodeAction {
13039                    kind: Some("code-action-2".into()),
13040                    edit: Some(lsp::WorkspaceEdit::new(
13041                        [(
13042                            uri,
13043                            vec![lsp::TextEdit::new(
13044                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
13045                                "applied-code-action-2-edit\n".to_string(),
13046                            )],
13047                        )]
13048                        .into_iter()
13049                        .collect(),
13050                    )),
13051                    ..Default::default()
13052                },
13053                req => panic!("Unexpected code action request: {:?}", req),
13054            };
13055            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
13056                code_action,
13057            )]))
13058        },
13059    );
13060
13061    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
13062        move |params, _| async move { Ok(params) }
13063    });
13064
13065    let command_lock = Arc::new(futures::lock::Mutex::new(()));
13066    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
13067        let fake = fake_server.clone();
13068        let lock = command_lock.clone();
13069        move |params, _| {
13070            assert_eq!(params.command, "the-command-for-code-action-1");
13071            let fake = fake.clone();
13072            let lock = lock.clone();
13073            async move {
13074                lock.lock().await;
13075                fake.server
13076                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
13077                        label: None,
13078                        edit: lsp::WorkspaceEdit {
13079                            changes: Some(
13080                                [(
13081                                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
13082                                    vec![lsp::TextEdit {
13083                                        range: lsp::Range::new(
13084                                            lsp::Position::new(0, 0),
13085                                            lsp::Position::new(0, 0),
13086                                        ),
13087                                        new_text: "applied-code-action-1-command\n".into(),
13088                                    }],
13089                                )]
13090                                .into_iter()
13091                                .collect(),
13092                            ),
13093                            ..Default::default()
13094                        },
13095                    })
13096                    .await
13097                    .into_response()
13098                    .unwrap();
13099                Ok(Some(json!(null)))
13100            }
13101        }
13102    });
13103
13104    editor
13105        .update_in(cx, |editor, window, cx| {
13106            editor.perform_format(
13107                project.clone(),
13108                FormatTrigger::Manual,
13109                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
13110                window,
13111                cx,
13112            )
13113        })
13114        .unwrap()
13115        .await;
13116    editor.update(cx, |editor, cx| {
13117        assert_eq!(
13118            editor.text(cx),
13119            r#"
13120                applied-code-action-2-edit
13121                applied-code-action-1-command
13122                applied-code-action-1-edit
13123                applied-formatting
13124                one
13125                two
13126                three
13127            "#
13128            .unindent()
13129        );
13130    });
13131
13132    editor.update_in(cx, |editor, window, cx| {
13133        editor.undo(&Default::default(), window, cx);
13134        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
13135    });
13136
13137    // Perform a manual edit while waiting for an LSP command
13138    // that's being run as part of a formatting code action.
13139    let lock_guard = command_lock.lock().await;
13140    let format = editor
13141        .update_in(cx, |editor, window, cx| {
13142            editor.perform_format(
13143                project.clone(),
13144                FormatTrigger::Manual,
13145                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
13146                window,
13147                cx,
13148            )
13149        })
13150        .unwrap();
13151    cx.run_until_parked();
13152    editor.update(cx, |editor, cx| {
13153        assert_eq!(
13154            editor.text(cx),
13155            r#"
13156                applied-code-action-1-edit
13157                applied-formatting
13158                one
13159                two
13160                three
13161            "#
13162            .unindent()
13163        );
13164
13165        editor.buffer.update(cx, |buffer, cx| {
13166            let ix = buffer.len(cx);
13167            buffer.edit([(ix..ix, "edited\n")], None, cx);
13168        });
13169    });
13170
13171    // Allow the LSP command to proceed. Because the buffer was edited,
13172    // the second code action will not be run.
13173    drop(lock_guard);
13174    format.await;
13175    editor.update_in(cx, |editor, window, cx| {
13176        assert_eq!(
13177            editor.text(cx),
13178            r#"
13179                applied-code-action-1-command
13180                applied-code-action-1-edit
13181                applied-formatting
13182                one
13183                two
13184                three
13185                edited
13186            "#
13187            .unindent()
13188        );
13189
13190        // The manual edit is undone first, because it is the last thing the user did
13191        // (even though the command completed afterwards).
13192        editor.undo(&Default::default(), window, cx);
13193        assert_eq!(
13194            editor.text(cx),
13195            r#"
13196                applied-code-action-1-command
13197                applied-code-action-1-edit
13198                applied-formatting
13199                one
13200                two
13201                three
13202            "#
13203            .unindent()
13204        );
13205
13206        // All the formatting (including the command, which completed after the manual edit)
13207        // is undone together.
13208        editor.undo(&Default::default(), window, cx);
13209        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
13210    });
13211}
13212
13213#[gpui::test]
13214async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
13215    init_test(cx, |settings| {
13216        settings.defaults.formatter = Some(FormatterList::Vec(vec![Formatter::LanguageServer(
13217            settings::LanguageServerFormatterSpecifier::Current,
13218        )]))
13219    });
13220
13221    let fs = FakeFs::new(cx.executor());
13222    fs.insert_file(path!("/file.ts"), Default::default()).await;
13223
13224    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
13225
13226    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13227    language_registry.add(Arc::new(Language::new(
13228        LanguageConfig {
13229            name: "TypeScript".into(),
13230            matcher: LanguageMatcher {
13231                path_suffixes: vec!["ts".to_string()],
13232                ..Default::default()
13233            },
13234            ..LanguageConfig::default()
13235        },
13236        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
13237    )));
13238    update_test_language_settings(cx, |settings| {
13239        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
13240    });
13241    let mut fake_servers = language_registry.register_fake_lsp(
13242        "TypeScript",
13243        FakeLspAdapter {
13244            capabilities: lsp::ServerCapabilities {
13245                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
13246                ..Default::default()
13247            },
13248            ..Default::default()
13249        },
13250    );
13251
13252    let buffer = project
13253        .update(cx, |project, cx| {
13254            project.open_local_buffer(path!("/file.ts"), cx)
13255        })
13256        .await
13257        .unwrap();
13258
13259    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
13260    let (editor, cx) = cx.add_window_view(|window, cx| {
13261        build_editor_with_project(project.clone(), buffer, window, cx)
13262    });
13263    editor.update_in(cx, |editor, window, cx| {
13264        editor.set_text(
13265            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
13266            window,
13267            cx,
13268        )
13269    });
13270
13271    let fake_server = fake_servers.next().await.unwrap();
13272
13273    let format = editor
13274        .update_in(cx, |editor, window, cx| {
13275            editor.perform_code_action_kind(
13276                project.clone(),
13277                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
13278                window,
13279                cx,
13280            )
13281        })
13282        .unwrap();
13283    fake_server
13284        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
13285            assert_eq!(
13286                params.text_document.uri,
13287                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
13288            );
13289            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
13290                lsp::CodeAction {
13291                    title: "Organize Imports".to_string(),
13292                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
13293                    edit: Some(lsp::WorkspaceEdit {
13294                        changes: Some(
13295                            [(
13296                                params.text_document.uri.clone(),
13297                                vec![lsp::TextEdit::new(
13298                                    lsp::Range::new(
13299                                        lsp::Position::new(1, 0),
13300                                        lsp::Position::new(2, 0),
13301                                    ),
13302                                    "".to_string(),
13303                                )],
13304                            )]
13305                            .into_iter()
13306                            .collect(),
13307                        ),
13308                        ..Default::default()
13309                    }),
13310                    ..Default::default()
13311                },
13312            )]))
13313        })
13314        .next()
13315        .await;
13316    format.await;
13317    assert_eq!(
13318        editor.update(cx, |editor, cx| editor.text(cx)),
13319        "import { a } from 'module';\n\nconst x = a;\n"
13320    );
13321
13322    editor.update_in(cx, |editor, window, cx| {
13323        editor.set_text(
13324            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
13325            window,
13326            cx,
13327        )
13328    });
13329    // Ensure we don't lock if code action hangs.
13330    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
13331        move |params, _| async move {
13332            assert_eq!(
13333                params.text_document.uri,
13334                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
13335            );
13336            futures::future::pending::<()>().await;
13337            unreachable!()
13338        },
13339    );
13340    let format = editor
13341        .update_in(cx, |editor, window, cx| {
13342            editor.perform_code_action_kind(
13343                project,
13344                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
13345                window,
13346                cx,
13347            )
13348        })
13349        .unwrap();
13350    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
13351    format.await;
13352    assert_eq!(
13353        editor.update(cx, |editor, cx| editor.text(cx)),
13354        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
13355    );
13356}
13357
13358#[gpui::test]
13359async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
13360    init_test(cx, |_| {});
13361
13362    let mut cx = EditorLspTestContext::new_rust(
13363        lsp::ServerCapabilities {
13364            document_formatting_provider: Some(lsp::OneOf::Left(true)),
13365            ..Default::default()
13366        },
13367        cx,
13368    )
13369    .await;
13370
13371    cx.set_state(indoc! {"
13372        one.twoˇ
13373    "});
13374
13375    // The format request takes a long time. When it completes, it inserts
13376    // a newline and an indent before the `.`
13377    cx.lsp
13378        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
13379            let executor = cx.background_executor().clone();
13380            async move {
13381                executor.timer(Duration::from_millis(100)).await;
13382                Ok(Some(vec![lsp::TextEdit {
13383                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
13384                    new_text: "\n    ".into(),
13385                }]))
13386            }
13387        });
13388
13389    // Submit a format request.
13390    let format_1 = cx
13391        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
13392        .unwrap();
13393    cx.executor().run_until_parked();
13394
13395    // Submit a second format request.
13396    let format_2 = cx
13397        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
13398        .unwrap();
13399    cx.executor().run_until_parked();
13400
13401    // Wait for both format requests to complete
13402    cx.executor().advance_clock(Duration::from_millis(200));
13403    format_1.await.unwrap();
13404    format_2.await.unwrap();
13405
13406    // The formatting edits only happens once.
13407    cx.assert_editor_state(indoc! {"
13408        one
13409            .twoˇ
13410    "});
13411}
13412
13413#[gpui::test]
13414async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
13415    init_test(cx, |settings| {
13416        settings.defaults.formatter = Some(FormatterList::default())
13417    });
13418
13419    let mut cx = EditorLspTestContext::new_rust(
13420        lsp::ServerCapabilities {
13421            document_formatting_provider: Some(lsp::OneOf::Left(true)),
13422            ..Default::default()
13423        },
13424        cx,
13425    )
13426    .await;
13427
13428    // Record which buffer changes have been sent to the language server
13429    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
13430    cx.lsp
13431        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
13432            let buffer_changes = buffer_changes.clone();
13433            move |params, _| {
13434                buffer_changes.lock().extend(
13435                    params
13436                        .content_changes
13437                        .into_iter()
13438                        .map(|e| (e.range.unwrap(), e.text)),
13439                );
13440            }
13441        });
13442    // Handle formatting requests to the language server.
13443    cx.lsp
13444        .set_request_handler::<lsp::request::Formatting, _, _>({
13445            move |_, _| {
13446                // Insert blank lines between each line of the buffer.
13447                async move {
13448                    // TODO: this assertion is not reliably true. Currently nothing guarantees that we deliver
13449                    // DidChangedTextDocument to the LSP before sending the formatting request.
13450                    // assert_eq!(
13451                    //     &buffer_changes.lock()[1..],
13452                    //     &[
13453                    //         (
13454                    //             lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
13455                    //             "".into()
13456                    //         ),
13457                    //         (
13458                    //             lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
13459                    //             "".into()
13460                    //         ),
13461                    //         (
13462                    //             lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
13463                    //             "\n".into()
13464                    //         ),
13465                    //     ]
13466                    // );
13467
13468                    Ok(Some(vec![
13469                        lsp::TextEdit {
13470                            range: lsp::Range::new(
13471                                lsp::Position::new(1, 0),
13472                                lsp::Position::new(1, 0),
13473                            ),
13474                            new_text: "\n".into(),
13475                        },
13476                        lsp::TextEdit {
13477                            range: lsp::Range::new(
13478                                lsp::Position::new(2, 0),
13479                                lsp::Position::new(2, 0),
13480                            ),
13481                            new_text: "\n".into(),
13482                        },
13483                    ]))
13484                }
13485            }
13486        });
13487
13488    // Set up a buffer white some trailing whitespace and no trailing newline.
13489    cx.set_state(
13490        &[
13491            "one ",   //
13492            "twoˇ",   //
13493            "three ", //
13494            "four",   //
13495        ]
13496        .join("\n"),
13497    );
13498
13499    // Submit a format request.
13500    let format = cx
13501        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
13502        .unwrap();
13503
13504    cx.run_until_parked();
13505    // After formatting the buffer, the trailing whitespace is stripped,
13506    // a newline is appended, and the edits provided by the language server
13507    // have been applied.
13508    format.await.unwrap();
13509
13510    cx.assert_editor_state(
13511        &[
13512            "one",   //
13513            "",      //
13514            "twoˇ",  //
13515            "",      //
13516            "three", //
13517            "four",  //
13518            "",      //
13519        ]
13520        .join("\n"),
13521    );
13522
13523    // Undoing the formatting undoes the trailing whitespace removal, the
13524    // trailing newline, and the LSP edits.
13525    cx.update_buffer(|buffer, cx| buffer.undo(cx));
13526    cx.assert_editor_state(
13527        &[
13528            "one ",   //
13529            "twoˇ",   //
13530            "three ", //
13531            "four",   //
13532        ]
13533        .join("\n"),
13534    );
13535}
13536
13537#[gpui::test]
13538async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
13539    cx: &mut TestAppContext,
13540) {
13541    init_test(cx, |_| {});
13542
13543    cx.update(|cx| {
13544        cx.update_global::<SettingsStore, _>(|settings, cx| {
13545            settings.update_user_settings(cx, |settings| {
13546                settings.editor.auto_signature_help = Some(true);
13547                settings.editor.hover_popover_delay = Some(DelayMs(300));
13548            });
13549        });
13550    });
13551
13552    let mut cx = EditorLspTestContext::new_rust(
13553        lsp::ServerCapabilities {
13554            signature_help_provider: Some(lsp::SignatureHelpOptions {
13555                ..Default::default()
13556            }),
13557            ..Default::default()
13558        },
13559        cx,
13560    )
13561    .await;
13562
13563    let language = Language::new(
13564        LanguageConfig {
13565            name: "Rust".into(),
13566            brackets: BracketPairConfig {
13567                pairs: vec![
13568                    BracketPair {
13569                        start: "{".to_string(),
13570                        end: "}".to_string(),
13571                        close: true,
13572                        surround: true,
13573                        newline: true,
13574                    },
13575                    BracketPair {
13576                        start: "(".to_string(),
13577                        end: ")".to_string(),
13578                        close: true,
13579                        surround: true,
13580                        newline: true,
13581                    },
13582                    BracketPair {
13583                        start: "/*".to_string(),
13584                        end: " */".to_string(),
13585                        close: true,
13586                        surround: true,
13587                        newline: true,
13588                    },
13589                    BracketPair {
13590                        start: "[".to_string(),
13591                        end: "]".to_string(),
13592                        close: false,
13593                        surround: false,
13594                        newline: true,
13595                    },
13596                    BracketPair {
13597                        start: "\"".to_string(),
13598                        end: "\"".to_string(),
13599                        close: true,
13600                        surround: true,
13601                        newline: false,
13602                    },
13603                    BracketPair {
13604                        start: "<".to_string(),
13605                        end: ">".to_string(),
13606                        close: false,
13607                        surround: true,
13608                        newline: true,
13609                    },
13610                ],
13611                ..Default::default()
13612            },
13613            autoclose_before: "})]".to_string(),
13614            ..Default::default()
13615        },
13616        Some(tree_sitter_rust::LANGUAGE.into()),
13617    );
13618    let language = Arc::new(language);
13619
13620    cx.language_registry().add(language.clone());
13621    cx.update_buffer(|buffer, cx| {
13622        buffer.set_language(Some(language), cx);
13623    });
13624
13625    cx.set_state(
13626        &r#"
13627            fn main() {
13628                sampleˇ
13629            }
13630        "#
13631        .unindent(),
13632    );
13633
13634    cx.update_editor(|editor, window, cx| {
13635        editor.handle_input("(", window, cx);
13636    });
13637    cx.assert_editor_state(
13638        &"
13639            fn main() {
13640                sample(ˇ)
13641            }
13642        "
13643        .unindent(),
13644    );
13645
13646    let mocked_response = lsp::SignatureHelp {
13647        signatures: vec![lsp::SignatureInformation {
13648            label: "fn sample(param1: u8, param2: u8)".to_string(),
13649            documentation: None,
13650            parameters: Some(vec![
13651                lsp::ParameterInformation {
13652                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13653                    documentation: None,
13654                },
13655                lsp::ParameterInformation {
13656                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13657                    documentation: None,
13658                },
13659            ]),
13660            active_parameter: None,
13661        }],
13662        active_signature: Some(0),
13663        active_parameter: Some(0),
13664    };
13665    handle_signature_help_request(&mut cx, mocked_response).await;
13666
13667    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13668        .await;
13669
13670    cx.editor(|editor, _, _| {
13671        let signature_help_state = editor.signature_help_state.popover().cloned();
13672        let signature = signature_help_state.unwrap();
13673        assert_eq!(
13674            signature.signatures[signature.current_signature].label,
13675            "fn sample(param1: u8, param2: u8)"
13676        );
13677    });
13678}
13679
13680#[gpui::test]
13681async fn test_signature_help_delay_only_for_auto(cx: &mut TestAppContext) {
13682    init_test(cx, |_| {});
13683
13684    let delay_ms = 500;
13685    cx.update(|cx| {
13686        cx.update_global::<SettingsStore, _>(|settings, cx| {
13687            settings.update_user_settings(cx, |settings| {
13688                settings.editor.auto_signature_help = Some(true);
13689                settings.editor.show_signature_help_after_edits = Some(false);
13690                settings.editor.hover_popover_delay = Some(DelayMs(delay_ms));
13691            });
13692        });
13693    });
13694
13695    let mut cx = EditorLspTestContext::new_rust(
13696        lsp::ServerCapabilities {
13697            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13698            ..lsp::ServerCapabilities::default()
13699        },
13700        cx,
13701    )
13702    .await;
13703
13704    let mocked_response = lsp::SignatureHelp {
13705        signatures: vec![lsp::SignatureInformation {
13706            label: "fn sample(param1: u8)".to_string(),
13707            documentation: None,
13708            parameters: Some(vec![lsp::ParameterInformation {
13709                label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13710                documentation: None,
13711            }]),
13712            active_parameter: None,
13713        }],
13714        active_signature: Some(0),
13715        active_parameter: Some(0),
13716    };
13717
13718    cx.set_state(indoc! {"
13719        fn main() {
13720            sample(ˇ);
13721        }
13722
13723        fn sample(param1: u8) {}
13724    "});
13725
13726    // Manual trigger should show immediately without delay
13727    cx.update_editor(|editor, window, cx| {
13728        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13729    });
13730    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13731    cx.run_until_parked();
13732    cx.editor(|editor, _, _| {
13733        assert!(
13734            editor.signature_help_state.is_shown(),
13735            "Manual trigger should show signature help without delay"
13736        );
13737    });
13738
13739    cx.update_editor(|editor, _, cx| {
13740        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
13741    });
13742    cx.run_until_parked();
13743    cx.editor(|editor, _, _| {
13744        assert!(!editor.signature_help_state.is_shown());
13745    });
13746
13747    // Auto trigger (cursor movement into brackets) should respect delay
13748    cx.set_state(indoc! {"
13749        fn main() {
13750            sampleˇ();
13751        }
13752
13753        fn sample(param1: u8) {}
13754    "});
13755    cx.update_editor(|editor, window, cx| {
13756        editor.move_right(&MoveRight, window, cx);
13757    });
13758    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13759    cx.run_until_parked();
13760    cx.editor(|editor, _, _| {
13761        assert!(
13762            !editor.signature_help_state.is_shown(),
13763            "Auto trigger should wait for delay before showing signature help"
13764        );
13765    });
13766
13767    cx.executor()
13768        .advance_clock(Duration::from_millis(delay_ms + 50));
13769    cx.run_until_parked();
13770    cx.editor(|editor, _, _| {
13771        assert!(
13772            editor.signature_help_state.is_shown(),
13773            "Auto trigger should show signature help after delay elapsed"
13774        );
13775    });
13776}
13777
13778#[gpui::test]
13779async fn test_signature_help_after_edits_no_delay(cx: &mut TestAppContext) {
13780    init_test(cx, |_| {});
13781
13782    let delay_ms = 500;
13783    cx.update(|cx| {
13784        cx.update_global::<SettingsStore, _>(|settings, cx| {
13785            settings.update_user_settings(cx, |settings| {
13786                settings.editor.auto_signature_help = Some(false);
13787                settings.editor.show_signature_help_after_edits = Some(true);
13788                settings.editor.hover_popover_delay = Some(DelayMs(delay_ms));
13789            });
13790        });
13791    });
13792
13793    let mut cx = EditorLspTestContext::new_rust(
13794        lsp::ServerCapabilities {
13795            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13796            ..lsp::ServerCapabilities::default()
13797        },
13798        cx,
13799    )
13800    .await;
13801
13802    let language = Arc::new(Language::new(
13803        LanguageConfig {
13804            name: "Rust".into(),
13805            brackets: BracketPairConfig {
13806                pairs: vec![BracketPair {
13807                    start: "(".to_string(),
13808                    end: ")".to_string(),
13809                    close: true,
13810                    surround: true,
13811                    newline: true,
13812                }],
13813                ..BracketPairConfig::default()
13814            },
13815            autoclose_before: "})".to_string(),
13816            ..LanguageConfig::default()
13817        },
13818        Some(tree_sitter_rust::LANGUAGE.into()),
13819    ));
13820    cx.language_registry().add(language.clone());
13821    cx.update_buffer(|buffer, cx| {
13822        buffer.set_language(Some(language), cx);
13823    });
13824
13825    let mocked_response = lsp::SignatureHelp {
13826        signatures: vec![lsp::SignatureInformation {
13827            label: "fn sample(param1: u8)".to_string(),
13828            documentation: None,
13829            parameters: Some(vec![lsp::ParameterInformation {
13830                label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13831                documentation: None,
13832            }]),
13833            active_parameter: None,
13834        }],
13835        active_signature: Some(0),
13836        active_parameter: Some(0),
13837    };
13838
13839    cx.set_state(indoc! {"
13840        fn main() {
13841            sampleˇ
13842        }
13843    "});
13844
13845    // Typing bracket should show signature help immediately without delay
13846    cx.update_editor(|editor, window, cx| {
13847        editor.handle_input("(", window, cx);
13848    });
13849    handle_signature_help_request(&mut cx, mocked_response).await;
13850    cx.run_until_parked();
13851    cx.editor(|editor, _, _| {
13852        assert!(
13853            editor.signature_help_state.is_shown(),
13854            "show_signature_help_after_edits should show signature help without delay"
13855        );
13856    });
13857}
13858
13859#[gpui::test]
13860async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
13861    init_test(cx, |_| {});
13862
13863    cx.update(|cx| {
13864        cx.update_global::<SettingsStore, _>(|settings, cx| {
13865            settings.update_user_settings(cx, |settings| {
13866                settings.editor.auto_signature_help = Some(false);
13867                settings.editor.show_signature_help_after_edits = Some(false);
13868            });
13869        });
13870    });
13871
13872    let mut cx = EditorLspTestContext::new_rust(
13873        lsp::ServerCapabilities {
13874            signature_help_provider: Some(lsp::SignatureHelpOptions {
13875                ..Default::default()
13876            }),
13877            ..Default::default()
13878        },
13879        cx,
13880    )
13881    .await;
13882
13883    let language = Language::new(
13884        LanguageConfig {
13885            name: "Rust".into(),
13886            brackets: BracketPairConfig {
13887                pairs: vec![
13888                    BracketPair {
13889                        start: "{".to_string(),
13890                        end: "}".to_string(),
13891                        close: true,
13892                        surround: true,
13893                        newline: true,
13894                    },
13895                    BracketPair {
13896                        start: "(".to_string(),
13897                        end: ")".to_string(),
13898                        close: true,
13899                        surround: true,
13900                        newline: true,
13901                    },
13902                    BracketPair {
13903                        start: "/*".to_string(),
13904                        end: " */".to_string(),
13905                        close: true,
13906                        surround: true,
13907                        newline: true,
13908                    },
13909                    BracketPair {
13910                        start: "[".to_string(),
13911                        end: "]".to_string(),
13912                        close: false,
13913                        surround: false,
13914                        newline: true,
13915                    },
13916                    BracketPair {
13917                        start: "\"".to_string(),
13918                        end: "\"".to_string(),
13919                        close: true,
13920                        surround: true,
13921                        newline: false,
13922                    },
13923                    BracketPair {
13924                        start: "<".to_string(),
13925                        end: ">".to_string(),
13926                        close: false,
13927                        surround: true,
13928                        newline: true,
13929                    },
13930                ],
13931                ..Default::default()
13932            },
13933            autoclose_before: "})]".to_string(),
13934            ..Default::default()
13935        },
13936        Some(tree_sitter_rust::LANGUAGE.into()),
13937    );
13938    let language = Arc::new(language);
13939
13940    cx.language_registry().add(language.clone());
13941    cx.update_buffer(|buffer, cx| {
13942        buffer.set_language(Some(language), cx);
13943    });
13944
13945    // Ensure that signature_help is not called when no signature help is enabled.
13946    cx.set_state(
13947        &r#"
13948            fn main() {
13949                sampleˇ
13950            }
13951        "#
13952        .unindent(),
13953    );
13954    cx.update_editor(|editor, window, cx| {
13955        editor.handle_input("(", window, cx);
13956    });
13957    cx.assert_editor_state(
13958        &"
13959            fn main() {
13960                sample(ˇ)
13961            }
13962        "
13963        .unindent(),
13964    );
13965    cx.editor(|editor, _, _| {
13966        assert!(editor.signature_help_state.task().is_none());
13967    });
13968
13969    let mocked_response = lsp::SignatureHelp {
13970        signatures: vec![lsp::SignatureInformation {
13971            label: "fn sample(param1: u8, param2: u8)".to_string(),
13972            documentation: None,
13973            parameters: Some(vec![
13974                lsp::ParameterInformation {
13975                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13976                    documentation: None,
13977                },
13978                lsp::ParameterInformation {
13979                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13980                    documentation: None,
13981                },
13982            ]),
13983            active_parameter: None,
13984        }],
13985        active_signature: Some(0),
13986        active_parameter: Some(0),
13987    };
13988
13989    // Ensure that signature_help is called when enabled afte edits
13990    cx.update(|_, cx| {
13991        cx.update_global::<SettingsStore, _>(|settings, cx| {
13992            settings.update_user_settings(cx, |settings| {
13993                settings.editor.auto_signature_help = Some(false);
13994                settings.editor.show_signature_help_after_edits = Some(true);
13995            });
13996        });
13997    });
13998    cx.set_state(
13999        &r#"
14000            fn main() {
14001                sampleˇ
14002            }
14003        "#
14004        .unindent(),
14005    );
14006    cx.update_editor(|editor, window, cx| {
14007        editor.handle_input("(", window, cx);
14008    });
14009    cx.assert_editor_state(
14010        &"
14011            fn main() {
14012                sample(ˇ)
14013            }
14014        "
14015        .unindent(),
14016    );
14017    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
14018    cx.condition(|editor, _| editor.signature_help_state.is_shown())
14019        .await;
14020    cx.update_editor(|editor, _, _| {
14021        let signature_help_state = editor.signature_help_state.popover().cloned();
14022        assert!(signature_help_state.is_some());
14023        let signature = signature_help_state.unwrap();
14024        assert_eq!(
14025            signature.signatures[signature.current_signature].label,
14026            "fn sample(param1: u8, param2: u8)"
14027        );
14028        editor.signature_help_state = SignatureHelpState::default();
14029    });
14030
14031    // Ensure that signature_help is called when auto signature help override is enabled
14032    cx.update(|_, cx| {
14033        cx.update_global::<SettingsStore, _>(|settings, cx| {
14034            settings.update_user_settings(cx, |settings| {
14035                settings.editor.auto_signature_help = Some(true);
14036                settings.editor.show_signature_help_after_edits = Some(false);
14037            });
14038        });
14039    });
14040    cx.set_state(
14041        &r#"
14042            fn main() {
14043                sampleˇ
14044            }
14045        "#
14046        .unindent(),
14047    );
14048    cx.update_editor(|editor, window, cx| {
14049        editor.handle_input("(", window, cx);
14050    });
14051    cx.assert_editor_state(
14052        &"
14053            fn main() {
14054                sample(ˇ)
14055            }
14056        "
14057        .unindent(),
14058    );
14059    handle_signature_help_request(&mut cx, mocked_response).await;
14060    cx.condition(|editor, _| editor.signature_help_state.is_shown())
14061        .await;
14062    cx.editor(|editor, _, _| {
14063        let signature_help_state = editor.signature_help_state.popover().cloned();
14064        assert!(signature_help_state.is_some());
14065        let signature = signature_help_state.unwrap();
14066        assert_eq!(
14067            signature.signatures[signature.current_signature].label,
14068            "fn sample(param1: u8, param2: u8)"
14069        );
14070    });
14071}
14072
14073#[gpui::test]
14074async fn test_signature_help(cx: &mut TestAppContext) {
14075    init_test(cx, |_| {});
14076    cx.update(|cx| {
14077        cx.update_global::<SettingsStore, _>(|settings, cx| {
14078            settings.update_user_settings(cx, |settings| {
14079                settings.editor.auto_signature_help = Some(true);
14080            });
14081        });
14082    });
14083
14084    let mut cx = EditorLspTestContext::new_rust(
14085        lsp::ServerCapabilities {
14086            signature_help_provider: Some(lsp::SignatureHelpOptions {
14087                ..Default::default()
14088            }),
14089            ..Default::default()
14090        },
14091        cx,
14092    )
14093    .await;
14094
14095    // A test that directly calls `show_signature_help`
14096    cx.update_editor(|editor, window, cx| {
14097        editor.show_signature_help(&ShowSignatureHelp, window, cx);
14098    });
14099
14100    let mocked_response = lsp::SignatureHelp {
14101        signatures: vec![lsp::SignatureInformation {
14102            label: "fn sample(param1: u8, param2: u8)".to_string(),
14103            documentation: None,
14104            parameters: Some(vec![
14105                lsp::ParameterInformation {
14106                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
14107                    documentation: None,
14108                },
14109                lsp::ParameterInformation {
14110                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
14111                    documentation: None,
14112                },
14113            ]),
14114            active_parameter: None,
14115        }],
14116        active_signature: Some(0),
14117        active_parameter: Some(0),
14118    };
14119    handle_signature_help_request(&mut cx, mocked_response).await;
14120
14121    cx.condition(|editor, _| editor.signature_help_state.is_shown())
14122        .await;
14123
14124    cx.editor(|editor, _, _| {
14125        let signature_help_state = editor.signature_help_state.popover().cloned();
14126        assert!(signature_help_state.is_some());
14127        let signature = signature_help_state.unwrap();
14128        assert_eq!(
14129            signature.signatures[signature.current_signature].label,
14130            "fn sample(param1: u8, param2: u8)"
14131        );
14132    });
14133
14134    // When exiting outside from inside the brackets, `signature_help` is closed.
14135    cx.set_state(indoc! {"
14136        fn main() {
14137            sample(ˇ);
14138        }
14139
14140        fn sample(param1: u8, param2: u8) {}
14141    "});
14142
14143    cx.update_editor(|editor, window, cx| {
14144        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14145            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
14146        });
14147    });
14148
14149    let mocked_response = lsp::SignatureHelp {
14150        signatures: Vec::new(),
14151        active_signature: None,
14152        active_parameter: None,
14153    };
14154    handle_signature_help_request(&mut cx, mocked_response).await;
14155
14156    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
14157        .await;
14158
14159    cx.editor(|editor, _, _| {
14160        assert!(!editor.signature_help_state.is_shown());
14161    });
14162
14163    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
14164    cx.set_state(indoc! {"
14165        fn main() {
14166            sample(ˇ);
14167        }
14168
14169        fn sample(param1: u8, param2: u8) {}
14170    "});
14171
14172    let mocked_response = lsp::SignatureHelp {
14173        signatures: vec![lsp::SignatureInformation {
14174            label: "fn sample(param1: u8, param2: u8)".to_string(),
14175            documentation: None,
14176            parameters: Some(vec![
14177                lsp::ParameterInformation {
14178                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
14179                    documentation: None,
14180                },
14181                lsp::ParameterInformation {
14182                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
14183                    documentation: None,
14184                },
14185            ]),
14186            active_parameter: None,
14187        }],
14188        active_signature: Some(0),
14189        active_parameter: Some(0),
14190    };
14191    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
14192    cx.condition(|editor, _| editor.signature_help_state.is_shown())
14193        .await;
14194    cx.editor(|editor, _, _| {
14195        assert!(editor.signature_help_state.is_shown());
14196    });
14197
14198    // Restore the popover with more parameter input
14199    cx.set_state(indoc! {"
14200        fn main() {
14201            sample(param1, param2ˇ);
14202        }
14203
14204        fn sample(param1: u8, param2: u8) {}
14205    "});
14206
14207    let mocked_response = lsp::SignatureHelp {
14208        signatures: vec![lsp::SignatureInformation {
14209            label: "fn sample(param1: u8, param2: u8)".to_string(),
14210            documentation: None,
14211            parameters: Some(vec![
14212                lsp::ParameterInformation {
14213                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
14214                    documentation: None,
14215                },
14216                lsp::ParameterInformation {
14217                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
14218                    documentation: None,
14219                },
14220            ]),
14221            active_parameter: None,
14222        }],
14223        active_signature: Some(0),
14224        active_parameter: Some(1),
14225    };
14226    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
14227    cx.condition(|editor, _| editor.signature_help_state.is_shown())
14228        .await;
14229
14230    // When selecting a range, the popover is gone.
14231    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
14232    cx.update_editor(|editor, window, cx| {
14233        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14234            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
14235        })
14236    });
14237    cx.assert_editor_state(indoc! {"
14238        fn main() {
14239            sample(param1, «ˇparam2»);
14240        }
14241
14242        fn sample(param1: u8, param2: u8) {}
14243    "});
14244    cx.editor(|editor, _, _| {
14245        assert!(!editor.signature_help_state.is_shown());
14246    });
14247
14248    // When unselecting again, the popover is back if within the brackets.
14249    cx.update_editor(|editor, window, cx| {
14250        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14251            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
14252        })
14253    });
14254    cx.assert_editor_state(indoc! {"
14255        fn main() {
14256            sample(param1, ˇparam2);
14257        }
14258
14259        fn sample(param1: u8, param2: u8) {}
14260    "});
14261    handle_signature_help_request(&mut cx, mocked_response).await;
14262    cx.condition(|editor, _| editor.signature_help_state.is_shown())
14263        .await;
14264    cx.editor(|editor, _, _| {
14265        assert!(editor.signature_help_state.is_shown());
14266    });
14267
14268    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
14269    cx.update_editor(|editor, window, cx| {
14270        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14271            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
14272            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
14273        })
14274    });
14275    cx.assert_editor_state(indoc! {"
14276        fn main() {
14277            sample(param1, ˇparam2);
14278        }
14279
14280        fn sample(param1: u8, param2: u8) {}
14281    "});
14282
14283    let mocked_response = lsp::SignatureHelp {
14284        signatures: vec![lsp::SignatureInformation {
14285            label: "fn sample(param1: u8, param2: u8)".to_string(),
14286            documentation: None,
14287            parameters: Some(vec![
14288                lsp::ParameterInformation {
14289                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
14290                    documentation: None,
14291                },
14292                lsp::ParameterInformation {
14293                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
14294                    documentation: None,
14295                },
14296            ]),
14297            active_parameter: None,
14298        }],
14299        active_signature: Some(0),
14300        active_parameter: Some(1),
14301    };
14302    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
14303    cx.condition(|editor, _| editor.signature_help_state.is_shown())
14304        .await;
14305    cx.update_editor(|editor, _, cx| {
14306        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
14307    });
14308    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
14309        .await;
14310    cx.update_editor(|editor, window, cx| {
14311        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14312            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
14313        })
14314    });
14315    cx.assert_editor_state(indoc! {"
14316        fn main() {
14317            sample(param1, «ˇparam2»);
14318        }
14319
14320        fn sample(param1: u8, param2: u8) {}
14321    "});
14322    cx.update_editor(|editor, window, cx| {
14323        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14324            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
14325        })
14326    });
14327    cx.assert_editor_state(indoc! {"
14328        fn main() {
14329            sample(param1, ˇparam2);
14330        }
14331
14332        fn sample(param1: u8, param2: u8) {}
14333    "});
14334    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
14335        .await;
14336}
14337
14338#[gpui::test]
14339async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
14340    init_test(cx, |_| {});
14341
14342    let mut cx = EditorLspTestContext::new_rust(
14343        lsp::ServerCapabilities {
14344            signature_help_provider: Some(lsp::SignatureHelpOptions {
14345                ..Default::default()
14346            }),
14347            ..Default::default()
14348        },
14349        cx,
14350    )
14351    .await;
14352
14353    cx.set_state(indoc! {"
14354        fn main() {
14355            overloadedˇ
14356        }
14357    "});
14358
14359    cx.update_editor(|editor, window, cx| {
14360        editor.handle_input("(", window, cx);
14361        editor.show_signature_help(&ShowSignatureHelp, window, cx);
14362    });
14363
14364    // Mock response with 3 signatures
14365    let mocked_response = lsp::SignatureHelp {
14366        signatures: vec![
14367            lsp::SignatureInformation {
14368                label: "fn overloaded(x: i32)".to_string(),
14369                documentation: None,
14370                parameters: Some(vec![lsp::ParameterInformation {
14371                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
14372                    documentation: None,
14373                }]),
14374                active_parameter: None,
14375            },
14376            lsp::SignatureInformation {
14377                label: "fn overloaded(x: i32, y: i32)".to_string(),
14378                documentation: None,
14379                parameters: Some(vec![
14380                    lsp::ParameterInformation {
14381                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
14382                        documentation: None,
14383                    },
14384                    lsp::ParameterInformation {
14385                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
14386                        documentation: None,
14387                    },
14388                ]),
14389                active_parameter: None,
14390            },
14391            lsp::SignatureInformation {
14392                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
14393                documentation: None,
14394                parameters: Some(vec![
14395                    lsp::ParameterInformation {
14396                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
14397                        documentation: None,
14398                    },
14399                    lsp::ParameterInformation {
14400                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
14401                        documentation: None,
14402                    },
14403                    lsp::ParameterInformation {
14404                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
14405                        documentation: None,
14406                    },
14407                ]),
14408                active_parameter: None,
14409            },
14410        ],
14411        active_signature: Some(1),
14412        active_parameter: Some(0),
14413    };
14414    handle_signature_help_request(&mut cx, mocked_response).await;
14415
14416    cx.condition(|editor, _| editor.signature_help_state.is_shown())
14417        .await;
14418
14419    // Verify we have multiple signatures and the right one is selected
14420    cx.editor(|editor, _, _| {
14421        let popover = editor.signature_help_state.popover().cloned().unwrap();
14422        assert_eq!(popover.signatures.len(), 3);
14423        // active_signature was 1, so that should be the current
14424        assert_eq!(popover.current_signature, 1);
14425        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
14426        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
14427        assert_eq!(
14428            popover.signatures[2].label,
14429            "fn overloaded(x: i32, y: i32, z: i32)"
14430        );
14431    });
14432
14433    // Test navigation functionality
14434    cx.update_editor(|editor, window, cx| {
14435        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
14436    });
14437
14438    cx.editor(|editor, _, _| {
14439        let popover = editor.signature_help_state.popover().cloned().unwrap();
14440        assert_eq!(popover.current_signature, 2);
14441    });
14442
14443    // Test wrap around
14444    cx.update_editor(|editor, window, cx| {
14445        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
14446    });
14447
14448    cx.editor(|editor, _, _| {
14449        let popover = editor.signature_help_state.popover().cloned().unwrap();
14450        assert_eq!(popover.current_signature, 0);
14451    });
14452
14453    // Test previous navigation
14454    cx.update_editor(|editor, window, cx| {
14455        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
14456    });
14457
14458    cx.editor(|editor, _, _| {
14459        let popover = editor.signature_help_state.popover().cloned().unwrap();
14460        assert_eq!(popover.current_signature, 2);
14461    });
14462}
14463
14464#[gpui::test]
14465async fn test_completion_mode(cx: &mut TestAppContext) {
14466    init_test(cx, |_| {});
14467    let mut cx = EditorLspTestContext::new_rust(
14468        lsp::ServerCapabilities {
14469            completion_provider: Some(lsp::CompletionOptions {
14470                resolve_provider: Some(true),
14471                ..Default::default()
14472            }),
14473            ..Default::default()
14474        },
14475        cx,
14476    )
14477    .await;
14478
14479    struct Run {
14480        run_description: &'static str,
14481        initial_state: String,
14482        buffer_marked_text: String,
14483        completion_label: &'static str,
14484        completion_text: &'static str,
14485        expected_with_insert_mode: String,
14486        expected_with_replace_mode: String,
14487        expected_with_replace_subsequence_mode: String,
14488        expected_with_replace_suffix_mode: String,
14489    }
14490
14491    let runs = [
14492        Run {
14493            run_description: "Start of word matches completion text",
14494            initial_state: "before ediˇ after".into(),
14495            buffer_marked_text: "before <edi|> after".into(),
14496            completion_label: "editor",
14497            completion_text: "editor",
14498            expected_with_insert_mode: "before editorˇ after".into(),
14499            expected_with_replace_mode: "before editorˇ after".into(),
14500            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
14501            expected_with_replace_suffix_mode: "before editorˇ after".into(),
14502        },
14503        Run {
14504            run_description: "Accept same text at the middle of the word",
14505            initial_state: "before ediˇtor after".into(),
14506            buffer_marked_text: "before <edi|tor> after".into(),
14507            completion_label: "editor",
14508            completion_text: "editor",
14509            expected_with_insert_mode: "before editorˇtor after".into(),
14510            expected_with_replace_mode: "before editorˇ after".into(),
14511            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
14512            expected_with_replace_suffix_mode: "before editorˇ after".into(),
14513        },
14514        Run {
14515            run_description: "End of word matches completion text -- cursor at end",
14516            initial_state: "before torˇ after".into(),
14517            buffer_marked_text: "before <tor|> after".into(),
14518            completion_label: "editor",
14519            completion_text: "editor",
14520            expected_with_insert_mode: "before editorˇ after".into(),
14521            expected_with_replace_mode: "before editorˇ after".into(),
14522            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
14523            expected_with_replace_suffix_mode: "before editorˇ after".into(),
14524        },
14525        Run {
14526            run_description: "End of word matches completion text -- cursor at start",
14527            initial_state: "before ˇtor after".into(),
14528            buffer_marked_text: "before <|tor> after".into(),
14529            completion_label: "editor",
14530            completion_text: "editor",
14531            expected_with_insert_mode: "before editorˇtor after".into(),
14532            expected_with_replace_mode: "before editorˇ after".into(),
14533            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
14534            expected_with_replace_suffix_mode: "before editorˇ after".into(),
14535        },
14536        Run {
14537            run_description: "Prepend text containing whitespace",
14538            initial_state: "pˇfield: bool".into(),
14539            buffer_marked_text: "<p|field>: bool".into(),
14540            completion_label: "pub ",
14541            completion_text: "pub ",
14542            expected_with_insert_mode: "pub ˇfield: bool".into(),
14543            expected_with_replace_mode: "pub ˇ: bool".into(),
14544            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
14545            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
14546        },
14547        Run {
14548            run_description: "Add element to start of list",
14549            initial_state: "[element_ˇelement_2]".into(),
14550            buffer_marked_text: "[<element_|element_2>]".into(),
14551            completion_label: "element_1",
14552            completion_text: "element_1",
14553            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
14554            expected_with_replace_mode: "[element_1ˇ]".into(),
14555            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
14556            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
14557        },
14558        Run {
14559            run_description: "Add element to start of list -- first and second elements are equal",
14560            initial_state: "[elˇelement]".into(),
14561            buffer_marked_text: "[<el|element>]".into(),
14562            completion_label: "element",
14563            completion_text: "element",
14564            expected_with_insert_mode: "[elementˇelement]".into(),
14565            expected_with_replace_mode: "[elementˇ]".into(),
14566            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
14567            expected_with_replace_suffix_mode: "[elementˇ]".into(),
14568        },
14569        Run {
14570            run_description: "Ends with matching suffix",
14571            initial_state: "SubˇError".into(),
14572            buffer_marked_text: "<Sub|Error>".into(),
14573            completion_label: "SubscriptionError",
14574            completion_text: "SubscriptionError",
14575            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
14576            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
14577            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
14578            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
14579        },
14580        Run {
14581            run_description: "Suffix is a subsequence -- contiguous",
14582            initial_state: "SubˇErr".into(),
14583            buffer_marked_text: "<Sub|Err>".into(),
14584            completion_label: "SubscriptionError",
14585            completion_text: "SubscriptionError",
14586            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
14587            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
14588            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
14589            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
14590        },
14591        Run {
14592            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
14593            initial_state: "Suˇscrirr".into(),
14594            buffer_marked_text: "<Su|scrirr>".into(),
14595            completion_label: "SubscriptionError",
14596            completion_text: "SubscriptionError",
14597            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
14598            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
14599            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
14600            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
14601        },
14602        Run {
14603            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
14604            initial_state: "foo(indˇix)".into(),
14605            buffer_marked_text: "foo(<ind|ix>)".into(),
14606            completion_label: "node_index",
14607            completion_text: "node_index",
14608            expected_with_insert_mode: "foo(node_indexˇix)".into(),
14609            expected_with_replace_mode: "foo(node_indexˇ)".into(),
14610            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
14611            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
14612        },
14613        Run {
14614            run_description: "Replace range ends before cursor - should extend to cursor",
14615            initial_state: "before editˇo after".into(),
14616            buffer_marked_text: "before <{ed}>it|o after".into(),
14617            completion_label: "editor",
14618            completion_text: "editor",
14619            expected_with_insert_mode: "before editorˇo after".into(),
14620            expected_with_replace_mode: "before editorˇo after".into(),
14621            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
14622            expected_with_replace_suffix_mode: "before editorˇo after".into(),
14623        },
14624        Run {
14625            run_description: "Uses label for suffix matching",
14626            initial_state: "before ediˇtor after".into(),
14627            buffer_marked_text: "before <edi|tor> after".into(),
14628            completion_label: "editor",
14629            completion_text: "editor()",
14630            expected_with_insert_mode: "before editor()ˇtor after".into(),
14631            expected_with_replace_mode: "before editor()ˇ after".into(),
14632            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
14633            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
14634        },
14635        Run {
14636            run_description: "Case insensitive subsequence and suffix matching",
14637            initial_state: "before EDiˇtoR after".into(),
14638            buffer_marked_text: "before <EDi|toR> after".into(),
14639            completion_label: "editor",
14640            completion_text: "editor",
14641            expected_with_insert_mode: "before editorˇtoR after".into(),
14642            expected_with_replace_mode: "before editorˇ after".into(),
14643            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
14644            expected_with_replace_suffix_mode: "before editorˇ after".into(),
14645        },
14646    ];
14647
14648    for run in runs {
14649        let run_variations = [
14650            (LspInsertMode::Insert, run.expected_with_insert_mode),
14651            (LspInsertMode::Replace, run.expected_with_replace_mode),
14652            (
14653                LspInsertMode::ReplaceSubsequence,
14654                run.expected_with_replace_subsequence_mode,
14655            ),
14656            (
14657                LspInsertMode::ReplaceSuffix,
14658                run.expected_with_replace_suffix_mode,
14659            ),
14660        ];
14661
14662        for (lsp_insert_mode, expected_text) in run_variations {
14663            eprintln!(
14664                "run = {:?}, mode = {lsp_insert_mode:.?}",
14665                run.run_description,
14666            );
14667
14668            update_test_language_settings(&mut cx, |settings| {
14669                settings.defaults.completions = Some(CompletionSettingsContent {
14670                    lsp_insert_mode: Some(lsp_insert_mode),
14671                    words: Some(WordsCompletionMode::Disabled),
14672                    words_min_length: Some(0),
14673                    ..Default::default()
14674                });
14675            });
14676
14677            cx.set_state(&run.initial_state);
14678
14679            // Set up resolve handler before showing completions, since resolve may be
14680            // triggered when menu becomes visible (for documentation), not just on confirm.
14681            cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(
14682                move |_, _, _| async move {
14683                    Ok(lsp::CompletionItem {
14684                        additional_text_edits: None,
14685                        ..Default::default()
14686                    })
14687                },
14688            );
14689
14690            cx.update_editor(|editor, window, cx| {
14691                editor.show_completions(&ShowCompletions, window, cx);
14692            });
14693
14694            let counter = Arc::new(AtomicUsize::new(0));
14695            handle_completion_request_with_insert_and_replace(
14696                &mut cx,
14697                &run.buffer_marked_text,
14698                vec![(run.completion_label, run.completion_text)],
14699                counter.clone(),
14700            )
14701            .await;
14702            cx.condition(|editor, _| editor.context_menu_visible())
14703                .await;
14704            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14705
14706            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14707                editor
14708                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
14709                    .unwrap()
14710            });
14711            cx.assert_editor_state(&expected_text);
14712            apply_additional_edits.await.unwrap();
14713        }
14714    }
14715}
14716
14717#[gpui::test]
14718async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
14719    init_test(cx, |_| {});
14720    let mut cx = EditorLspTestContext::new_rust(
14721        lsp::ServerCapabilities {
14722            completion_provider: Some(lsp::CompletionOptions {
14723                resolve_provider: Some(true),
14724                ..Default::default()
14725            }),
14726            ..Default::default()
14727        },
14728        cx,
14729    )
14730    .await;
14731
14732    let initial_state = "SubˇError";
14733    let buffer_marked_text = "<Sub|Error>";
14734    let completion_text = "SubscriptionError";
14735    let expected_with_insert_mode = "SubscriptionErrorˇError";
14736    let expected_with_replace_mode = "SubscriptionErrorˇ";
14737
14738    update_test_language_settings(&mut cx, |settings| {
14739        settings.defaults.completions = Some(CompletionSettingsContent {
14740            words: Some(WordsCompletionMode::Disabled),
14741            words_min_length: Some(0),
14742            // set the opposite here to ensure that the action is overriding the default behavior
14743            lsp_insert_mode: Some(LspInsertMode::Insert),
14744            ..Default::default()
14745        });
14746    });
14747
14748    cx.set_state(initial_state);
14749    cx.update_editor(|editor, window, cx| {
14750        editor.show_completions(&ShowCompletions, window, cx);
14751    });
14752
14753    let counter = Arc::new(AtomicUsize::new(0));
14754    handle_completion_request_with_insert_and_replace(
14755        &mut cx,
14756        buffer_marked_text,
14757        vec![(completion_text, completion_text)],
14758        counter.clone(),
14759    )
14760    .await;
14761    cx.condition(|editor, _| editor.context_menu_visible())
14762        .await;
14763    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14764
14765    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14766        editor
14767            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14768            .unwrap()
14769    });
14770    cx.assert_editor_state(expected_with_replace_mode);
14771    handle_resolve_completion_request(&mut cx, None).await;
14772    apply_additional_edits.await.unwrap();
14773
14774    update_test_language_settings(&mut cx, |settings| {
14775        settings.defaults.completions = Some(CompletionSettingsContent {
14776            words: Some(WordsCompletionMode::Disabled),
14777            words_min_length: Some(0),
14778            // set the opposite here to ensure that the action is overriding the default behavior
14779            lsp_insert_mode: Some(LspInsertMode::Replace),
14780            ..Default::default()
14781        });
14782    });
14783
14784    cx.set_state(initial_state);
14785    cx.update_editor(|editor, window, cx| {
14786        editor.show_completions(&ShowCompletions, window, cx);
14787    });
14788    handle_completion_request_with_insert_and_replace(
14789        &mut cx,
14790        buffer_marked_text,
14791        vec![(completion_text, completion_text)],
14792        counter.clone(),
14793    )
14794    .await;
14795    cx.condition(|editor, _| editor.context_menu_visible())
14796        .await;
14797    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14798
14799    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14800        editor
14801            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
14802            .unwrap()
14803    });
14804    cx.assert_editor_state(expected_with_insert_mode);
14805    handle_resolve_completion_request(&mut cx, None).await;
14806    apply_additional_edits.await.unwrap();
14807}
14808
14809#[gpui::test]
14810async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
14811    init_test(cx, |_| {});
14812    let mut cx = EditorLspTestContext::new_rust(
14813        lsp::ServerCapabilities {
14814            completion_provider: Some(lsp::CompletionOptions {
14815                resolve_provider: Some(true),
14816                ..Default::default()
14817            }),
14818            ..Default::default()
14819        },
14820        cx,
14821    )
14822    .await;
14823
14824    // scenario: surrounding text matches completion text
14825    let completion_text = "to_offset";
14826    let initial_state = indoc! {"
14827        1. buf.to_offˇsuffix
14828        2. buf.to_offˇsuf
14829        3. buf.to_offˇfix
14830        4. buf.to_offˇ
14831        5. into_offˇensive
14832        6. ˇsuffix
14833        7. let ˇ //
14834        8. aaˇzz
14835        9. buf.to_off«zzzzzˇ»suffix
14836        10. buf.«ˇzzzzz»suffix
14837        11. to_off«ˇzzzzz»
14838
14839        buf.to_offˇsuffix  // newest cursor
14840    "};
14841    let completion_marked_buffer = indoc! {"
14842        1. buf.to_offsuffix
14843        2. buf.to_offsuf
14844        3. buf.to_offfix
14845        4. buf.to_off
14846        5. into_offensive
14847        6. suffix
14848        7. let  //
14849        8. aazz
14850        9. buf.to_offzzzzzsuffix
14851        10. buf.zzzzzsuffix
14852        11. to_offzzzzz
14853
14854        buf.<to_off|suffix>  // newest cursor
14855    "};
14856    let expected = indoc! {"
14857        1. buf.to_offsetˇ
14858        2. buf.to_offsetˇsuf
14859        3. buf.to_offsetˇfix
14860        4. buf.to_offsetˇ
14861        5. into_offsetˇensive
14862        6. to_offsetˇsuffix
14863        7. let to_offsetˇ //
14864        8. aato_offsetˇzz
14865        9. buf.to_offsetˇ
14866        10. buf.to_offsetˇsuffix
14867        11. to_offsetˇ
14868
14869        buf.to_offsetˇ  // newest cursor
14870    "};
14871    cx.set_state(initial_state);
14872    cx.update_editor(|editor, window, cx| {
14873        editor.show_completions(&ShowCompletions, window, cx);
14874    });
14875    handle_completion_request_with_insert_and_replace(
14876        &mut cx,
14877        completion_marked_buffer,
14878        vec![(completion_text, completion_text)],
14879        Arc::new(AtomicUsize::new(0)),
14880    )
14881    .await;
14882    cx.condition(|editor, _| editor.context_menu_visible())
14883        .await;
14884    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14885        editor
14886            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14887            .unwrap()
14888    });
14889    cx.assert_editor_state(expected);
14890    handle_resolve_completion_request(&mut cx, None).await;
14891    apply_additional_edits.await.unwrap();
14892
14893    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
14894    let completion_text = "foo_and_bar";
14895    let initial_state = indoc! {"
14896        1. ooanbˇ
14897        2. zooanbˇ
14898        3. ooanbˇz
14899        4. zooanbˇz
14900        5. ooanˇ
14901        6. oanbˇ
14902
14903        ooanbˇ
14904    "};
14905    let completion_marked_buffer = indoc! {"
14906        1. ooanb
14907        2. zooanb
14908        3. ooanbz
14909        4. zooanbz
14910        5. ooan
14911        6. oanb
14912
14913        <ooanb|>
14914    "};
14915    let expected = indoc! {"
14916        1. foo_and_barˇ
14917        2. zfoo_and_barˇ
14918        3. foo_and_barˇz
14919        4. zfoo_and_barˇz
14920        5. ooanfoo_and_barˇ
14921        6. oanbfoo_and_barˇ
14922
14923        foo_and_barˇ
14924    "};
14925    cx.set_state(initial_state);
14926    cx.update_editor(|editor, window, cx| {
14927        editor.show_completions(&ShowCompletions, window, cx);
14928    });
14929    handle_completion_request_with_insert_and_replace(
14930        &mut cx,
14931        completion_marked_buffer,
14932        vec![(completion_text, completion_text)],
14933        Arc::new(AtomicUsize::new(0)),
14934    )
14935    .await;
14936    cx.condition(|editor, _| editor.context_menu_visible())
14937        .await;
14938    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14939        editor
14940            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14941            .unwrap()
14942    });
14943    cx.assert_editor_state(expected);
14944    handle_resolve_completion_request(&mut cx, None).await;
14945    apply_additional_edits.await.unwrap();
14946
14947    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
14948    // (expects the same as if it was inserted at the end)
14949    let completion_text = "foo_and_bar";
14950    let initial_state = indoc! {"
14951        1. ooˇanb
14952        2. zooˇanb
14953        3. ooˇanbz
14954        4. zooˇanbz
14955
14956        ooˇanb
14957    "};
14958    let completion_marked_buffer = indoc! {"
14959        1. ooanb
14960        2. zooanb
14961        3. ooanbz
14962        4. zooanbz
14963
14964        <oo|anb>
14965    "};
14966    let expected = indoc! {"
14967        1. foo_and_barˇ
14968        2. zfoo_and_barˇ
14969        3. foo_and_barˇz
14970        4. zfoo_and_barˇz
14971
14972        foo_and_barˇ
14973    "};
14974    cx.set_state(initial_state);
14975    cx.update_editor(|editor, window, cx| {
14976        editor.show_completions(&ShowCompletions, window, cx);
14977    });
14978    handle_completion_request_with_insert_and_replace(
14979        &mut cx,
14980        completion_marked_buffer,
14981        vec![(completion_text, completion_text)],
14982        Arc::new(AtomicUsize::new(0)),
14983    )
14984    .await;
14985    cx.condition(|editor, _| editor.context_menu_visible())
14986        .await;
14987    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14988        editor
14989            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14990            .unwrap()
14991    });
14992    cx.assert_editor_state(expected);
14993    handle_resolve_completion_request(&mut cx, None).await;
14994    apply_additional_edits.await.unwrap();
14995}
14996
14997// This used to crash
14998#[gpui::test]
14999async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
15000    init_test(cx, |_| {});
15001
15002    let buffer_text = indoc! {"
15003        fn main() {
15004            10.satu;
15005
15006            //
15007            // separate cursors so they open in different excerpts (manually reproducible)
15008            //
15009
15010            10.satu20;
15011        }
15012    "};
15013    let multibuffer_text_with_selections = indoc! {"
15014        fn main() {
15015            10.satuˇ;
15016
15017            //
15018
15019            //
15020
15021            10.satuˇ20;
15022        }
15023    "};
15024    let expected_multibuffer = indoc! {"
15025        fn main() {
15026            10.saturating_sub()ˇ;
15027
15028            //
15029
15030            //
15031
15032            10.saturating_sub()ˇ;
15033        }
15034    "};
15035
15036    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
15037    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
15038
15039    let fs = FakeFs::new(cx.executor());
15040    fs.insert_tree(
15041        path!("/a"),
15042        json!({
15043            "main.rs": buffer_text,
15044        }),
15045    )
15046    .await;
15047
15048    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15049    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15050    language_registry.add(rust_lang());
15051    let mut fake_servers = language_registry.register_fake_lsp(
15052        "Rust",
15053        FakeLspAdapter {
15054            capabilities: lsp::ServerCapabilities {
15055                completion_provider: Some(lsp::CompletionOptions {
15056                    resolve_provider: None,
15057                    ..lsp::CompletionOptions::default()
15058                }),
15059                ..lsp::ServerCapabilities::default()
15060            },
15061            ..FakeLspAdapter::default()
15062        },
15063    );
15064    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15065    let cx = &mut VisualTestContext::from_window(*workspace, cx);
15066    let buffer = project
15067        .update(cx, |project, cx| {
15068            project.open_local_buffer(path!("/a/main.rs"), cx)
15069        })
15070        .await
15071        .unwrap();
15072
15073    let multi_buffer = cx.new(|cx| {
15074        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
15075        multi_buffer.push_excerpts(
15076            buffer.clone(),
15077            [ExcerptRange::new(0..first_excerpt_end)],
15078            cx,
15079        );
15080        multi_buffer.push_excerpts(
15081            buffer.clone(),
15082            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
15083            cx,
15084        );
15085        multi_buffer
15086    });
15087
15088    let editor = workspace
15089        .update(cx, |_, window, cx| {
15090            cx.new(|cx| {
15091                Editor::new(
15092                    EditorMode::Full {
15093                        scale_ui_elements_with_buffer_font_size: false,
15094                        show_active_line_background: false,
15095                        sizing_behavior: SizingBehavior::Default,
15096                    },
15097                    multi_buffer.clone(),
15098                    Some(project.clone()),
15099                    window,
15100                    cx,
15101                )
15102            })
15103        })
15104        .unwrap();
15105
15106    let pane = workspace
15107        .update(cx, |workspace, _, _| workspace.active_pane().clone())
15108        .unwrap();
15109    pane.update_in(cx, |pane, window, cx| {
15110        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
15111    });
15112
15113    let fake_server = fake_servers.next().await.unwrap();
15114    cx.run_until_parked();
15115
15116    editor.update_in(cx, |editor, window, cx| {
15117        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15118            s.select_ranges([
15119                Point::new(1, 11)..Point::new(1, 11),
15120                Point::new(7, 11)..Point::new(7, 11),
15121            ])
15122        });
15123
15124        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
15125    });
15126
15127    editor.update_in(cx, |editor, window, cx| {
15128        editor.show_completions(&ShowCompletions, window, cx);
15129    });
15130
15131    fake_server
15132        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15133            let completion_item = lsp::CompletionItem {
15134                label: "saturating_sub()".into(),
15135                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
15136                    lsp::InsertReplaceEdit {
15137                        new_text: "saturating_sub()".to_owned(),
15138                        insert: lsp::Range::new(
15139                            lsp::Position::new(7, 7),
15140                            lsp::Position::new(7, 11),
15141                        ),
15142                        replace: lsp::Range::new(
15143                            lsp::Position::new(7, 7),
15144                            lsp::Position::new(7, 13),
15145                        ),
15146                    },
15147                )),
15148                ..lsp::CompletionItem::default()
15149            };
15150
15151            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
15152        })
15153        .next()
15154        .await
15155        .unwrap();
15156
15157    cx.condition(&editor, |editor, _| editor.context_menu_visible())
15158        .await;
15159
15160    editor
15161        .update_in(cx, |editor, window, cx| {
15162            editor
15163                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
15164                .unwrap()
15165        })
15166        .await
15167        .unwrap();
15168
15169    editor.update(cx, |editor, cx| {
15170        assert_text_with_selections(editor, expected_multibuffer, cx);
15171    })
15172}
15173
15174#[gpui::test]
15175async fn test_completion(cx: &mut TestAppContext) {
15176    init_test(cx, |_| {});
15177
15178    let mut cx = EditorLspTestContext::new_rust(
15179        lsp::ServerCapabilities {
15180            completion_provider: Some(lsp::CompletionOptions {
15181                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15182                resolve_provider: Some(true),
15183                ..Default::default()
15184            }),
15185            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15186            ..Default::default()
15187        },
15188        cx,
15189    )
15190    .await;
15191    let counter = Arc::new(AtomicUsize::new(0));
15192
15193    cx.set_state(indoc! {"
15194        oneˇ
15195        two
15196        three
15197    "});
15198    cx.simulate_keystroke(".");
15199    handle_completion_request(
15200        indoc! {"
15201            one.|<>
15202            two
15203            three
15204        "},
15205        vec!["first_completion", "second_completion"],
15206        true,
15207        counter.clone(),
15208        &mut cx,
15209    )
15210    .await;
15211    cx.condition(|editor, _| editor.context_menu_visible())
15212        .await;
15213    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15214
15215    let _handler = handle_signature_help_request(
15216        &mut cx,
15217        lsp::SignatureHelp {
15218            signatures: vec![lsp::SignatureInformation {
15219                label: "test signature".to_string(),
15220                documentation: None,
15221                parameters: Some(vec![lsp::ParameterInformation {
15222                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
15223                    documentation: None,
15224                }]),
15225                active_parameter: None,
15226            }],
15227            active_signature: None,
15228            active_parameter: None,
15229        },
15230    );
15231    cx.update_editor(|editor, window, cx| {
15232        assert!(
15233            !editor.signature_help_state.is_shown(),
15234            "No signature help was called for"
15235        );
15236        editor.show_signature_help(&ShowSignatureHelp, window, cx);
15237    });
15238    cx.run_until_parked();
15239    cx.update_editor(|editor, _, _| {
15240        assert!(
15241            !editor.signature_help_state.is_shown(),
15242            "No signature help should be shown when completions menu is open"
15243        );
15244    });
15245
15246    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
15247        editor.context_menu_next(&Default::default(), window, cx);
15248        editor
15249            .confirm_completion(&ConfirmCompletion::default(), window, cx)
15250            .unwrap()
15251    });
15252    cx.assert_editor_state(indoc! {"
15253        one.second_completionˇ
15254        two
15255        three
15256    "});
15257
15258    handle_resolve_completion_request(
15259        &mut cx,
15260        Some(vec![
15261            (
15262                //This overlaps with the primary completion edit which is
15263                //misbehavior from the LSP spec, test that we filter it out
15264                indoc! {"
15265                    one.second_ˇcompletion
15266                    two
15267                    threeˇ
15268                "},
15269                "overlapping additional edit",
15270            ),
15271            (
15272                indoc! {"
15273                    one.second_completion
15274                    two
15275                    threeˇ
15276                "},
15277                "\nadditional edit",
15278            ),
15279        ]),
15280    )
15281    .await;
15282    apply_additional_edits.await.unwrap();
15283    cx.assert_editor_state(indoc! {"
15284        one.second_completionˇ
15285        two
15286        three
15287        additional edit
15288    "});
15289
15290    cx.set_state(indoc! {"
15291        one.second_completion
15292        twoˇ
15293        threeˇ
15294        additional edit
15295    "});
15296    cx.simulate_keystroke(" ");
15297    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
15298    cx.simulate_keystroke("s");
15299    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
15300
15301    cx.assert_editor_state(indoc! {"
15302        one.second_completion
15303        two sˇ
15304        three sˇ
15305        additional edit
15306    "});
15307    handle_completion_request(
15308        indoc! {"
15309            one.second_completion
15310            two s
15311            three <s|>
15312            additional edit
15313        "},
15314        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
15315        true,
15316        counter.clone(),
15317        &mut cx,
15318    )
15319    .await;
15320    cx.condition(|editor, _| editor.context_menu_visible())
15321        .await;
15322    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
15323
15324    cx.simulate_keystroke("i");
15325
15326    handle_completion_request(
15327        indoc! {"
15328            one.second_completion
15329            two si
15330            three <si|>
15331            additional edit
15332        "},
15333        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
15334        true,
15335        counter.clone(),
15336        &mut cx,
15337    )
15338    .await;
15339    cx.condition(|editor, _| editor.context_menu_visible())
15340        .await;
15341    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
15342
15343    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
15344        editor
15345            .confirm_completion(&ConfirmCompletion::default(), window, cx)
15346            .unwrap()
15347    });
15348    cx.assert_editor_state(indoc! {"
15349        one.second_completion
15350        two sixth_completionˇ
15351        three sixth_completionˇ
15352        additional edit
15353    "});
15354
15355    apply_additional_edits.await.unwrap();
15356
15357    update_test_language_settings(&mut cx, |settings| {
15358        settings.defaults.show_completions_on_input = Some(false);
15359    });
15360    cx.set_state("editorˇ");
15361    cx.simulate_keystroke(".");
15362    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
15363    cx.simulate_keystrokes("c l o");
15364    cx.assert_editor_state("editor.cloˇ");
15365    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
15366    cx.update_editor(|editor, window, cx| {
15367        editor.show_completions(&ShowCompletions, window, cx);
15368    });
15369    handle_completion_request(
15370        "editor.<clo|>",
15371        vec!["close", "clobber"],
15372        true,
15373        counter.clone(),
15374        &mut cx,
15375    )
15376    .await;
15377    cx.condition(|editor, _| editor.context_menu_visible())
15378        .await;
15379    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
15380
15381    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
15382        editor
15383            .confirm_completion(&ConfirmCompletion::default(), window, cx)
15384            .unwrap()
15385    });
15386    cx.assert_editor_state("editor.clobberˇ");
15387    handle_resolve_completion_request(&mut cx, None).await;
15388    apply_additional_edits.await.unwrap();
15389}
15390
15391#[gpui::test]
15392async fn test_completion_can_run_commands(cx: &mut TestAppContext) {
15393    init_test(cx, |_| {});
15394
15395    let fs = FakeFs::new(cx.executor());
15396    fs.insert_tree(
15397        path!("/a"),
15398        json!({
15399            "main.rs": "",
15400        }),
15401    )
15402    .await;
15403
15404    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15405    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15406    language_registry.add(rust_lang());
15407    let command_calls = Arc::new(AtomicUsize::new(0));
15408    let registered_command = "_the/command";
15409
15410    let closure_command_calls = command_calls.clone();
15411    let mut fake_servers = language_registry.register_fake_lsp(
15412        "Rust",
15413        FakeLspAdapter {
15414            capabilities: lsp::ServerCapabilities {
15415                completion_provider: Some(lsp::CompletionOptions {
15416                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15417                    ..lsp::CompletionOptions::default()
15418                }),
15419                execute_command_provider: Some(lsp::ExecuteCommandOptions {
15420                    commands: vec![registered_command.to_owned()],
15421                    ..lsp::ExecuteCommandOptions::default()
15422                }),
15423                ..lsp::ServerCapabilities::default()
15424            },
15425            initializer: Some(Box::new(move |fake_server| {
15426                fake_server.set_request_handler::<lsp::request::Completion, _, _>(
15427                    move |params, _| async move {
15428                        Ok(Some(lsp::CompletionResponse::Array(vec![
15429                            lsp::CompletionItem {
15430                                label: "registered_command".to_owned(),
15431                                text_edit: gen_text_edit(&params, ""),
15432                                command: Some(lsp::Command {
15433                                    title: registered_command.to_owned(),
15434                                    command: "_the/command".to_owned(),
15435                                    arguments: Some(vec![serde_json::Value::Bool(true)]),
15436                                }),
15437                                ..lsp::CompletionItem::default()
15438                            },
15439                            lsp::CompletionItem {
15440                                label: "unregistered_command".to_owned(),
15441                                text_edit: gen_text_edit(&params, ""),
15442                                command: Some(lsp::Command {
15443                                    title: "????????????".to_owned(),
15444                                    command: "????????????".to_owned(),
15445                                    arguments: Some(vec![serde_json::Value::Null]),
15446                                }),
15447                                ..lsp::CompletionItem::default()
15448                            },
15449                        ])))
15450                    },
15451                );
15452                fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
15453                    let command_calls = closure_command_calls.clone();
15454                    move |params, _| {
15455                        assert_eq!(params.command, registered_command);
15456                        let command_calls = command_calls.clone();
15457                        async move {
15458                            command_calls.fetch_add(1, atomic::Ordering::Release);
15459                            Ok(Some(json!(null)))
15460                        }
15461                    }
15462                });
15463            })),
15464            ..FakeLspAdapter::default()
15465        },
15466    );
15467    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15468    let cx = &mut VisualTestContext::from_window(*workspace, cx);
15469    let editor = workspace
15470        .update(cx, |workspace, window, cx| {
15471            workspace.open_abs_path(
15472                PathBuf::from(path!("/a/main.rs")),
15473                OpenOptions::default(),
15474                window,
15475                cx,
15476            )
15477        })
15478        .unwrap()
15479        .await
15480        .unwrap()
15481        .downcast::<Editor>()
15482        .unwrap();
15483    let _fake_server = fake_servers.next().await.unwrap();
15484    cx.run_until_parked();
15485
15486    editor.update_in(cx, |editor, window, cx| {
15487        cx.focus_self(window);
15488        editor.move_to_end(&MoveToEnd, window, cx);
15489        editor.handle_input(".", window, cx);
15490    });
15491    cx.run_until_parked();
15492    editor.update(cx, |editor, _| {
15493        assert!(editor.context_menu_visible());
15494        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15495        {
15496            let completion_labels = menu
15497                .completions
15498                .borrow()
15499                .iter()
15500                .map(|c| c.label.text.clone())
15501                .collect::<Vec<_>>();
15502            assert_eq!(
15503                completion_labels,
15504                &["registered_command", "unregistered_command",],
15505            );
15506        } else {
15507            panic!("expected completion menu to be open");
15508        }
15509    });
15510
15511    editor
15512        .update_in(cx, |editor, window, cx| {
15513            editor
15514                .confirm_completion(&ConfirmCompletion::default(), window, cx)
15515                .unwrap()
15516        })
15517        .await
15518        .unwrap();
15519    cx.run_until_parked();
15520    assert_eq!(
15521        command_calls.load(atomic::Ordering::Acquire),
15522        1,
15523        "For completion with a registered command, Zed should send a command execution request",
15524    );
15525
15526    editor.update_in(cx, |editor, window, cx| {
15527        cx.focus_self(window);
15528        editor.handle_input(".", window, cx);
15529    });
15530    cx.run_until_parked();
15531    editor.update(cx, |editor, _| {
15532        assert!(editor.context_menu_visible());
15533        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15534        {
15535            let completion_labels = menu
15536                .completions
15537                .borrow()
15538                .iter()
15539                .map(|c| c.label.text.clone())
15540                .collect::<Vec<_>>();
15541            assert_eq!(
15542                completion_labels,
15543                &["registered_command", "unregistered_command",],
15544            );
15545        } else {
15546            panic!("expected completion menu to be open");
15547        }
15548    });
15549    editor
15550        .update_in(cx, |editor, window, cx| {
15551            editor.context_menu_next(&Default::default(), window, cx);
15552            editor
15553                .confirm_completion(&ConfirmCompletion::default(), window, cx)
15554                .unwrap()
15555        })
15556        .await
15557        .unwrap();
15558    cx.run_until_parked();
15559    assert_eq!(
15560        command_calls.load(atomic::Ordering::Acquire),
15561        1,
15562        "For completion with an unregistered command, Zed should not send a command execution request",
15563    );
15564}
15565
15566#[gpui::test]
15567async fn test_completion_reuse(cx: &mut TestAppContext) {
15568    init_test(cx, |_| {});
15569
15570    let mut cx = EditorLspTestContext::new_rust(
15571        lsp::ServerCapabilities {
15572            completion_provider: Some(lsp::CompletionOptions {
15573                trigger_characters: Some(vec![".".to_string()]),
15574                ..Default::default()
15575            }),
15576            ..Default::default()
15577        },
15578        cx,
15579    )
15580    .await;
15581
15582    let counter = Arc::new(AtomicUsize::new(0));
15583    cx.set_state("objˇ");
15584    cx.simulate_keystroke(".");
15585
15586    // Initial completion request returns complete results
15587    let is_incomplete = false;
15588    handle_completion_request(
15589        "obj.|<>",
15590        vec!["a", "ab", "abc"],
15591        is_incomplete,
15592        counter.clone(),
15593        &mut cx,
15594    )
15595    .await;
15596    cx.run_until_parked();
15597    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15598    cx.assert_editor_state("obj.ˇ");
15599    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
15600
15601    // Type "a" - filters existing completions
15602    cx.simulate_keystroke("a");
15603    cx.run_until_parked();
15604    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15605    cx.assert_editor_state("obj.aˇ");
15606    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
15607
15608    // Type "b" - filters existing completions
15609    cx.simulate_keystroke("b");
15610    cx.run_until_parked();
15611    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15612    cx.assert_editor_state("obj.abˇ");
15613    check_displayed_completions(vec!["ab", "abc"], &mut cx);
15614
15615    // Type "c" - filters existing completions
15616    cx.simulate_keystroke("c");
15617    cx.run_until_parked();
15618    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15619    cx.assert_editor_state("obj.abcˇ");
15620    check_displayed_completions(vec!["abc"], &mut cx);
15621
15622    // Backspace to delete "c" - filters existing completions
15623    cx.update_editor(|editor, window, cx| {
15624        editor.backspace(&Backspace, window, cx);
15625    });
15626    cx.run_until_parked();
15627    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15628    cx.assert_editor_state("obj.abˇ");
15629    check_displayed_completions(vec!["ab", "abc"], &mut cx);
15630
15631    // Moving cursor to the left dismisses menu.
15632    cx.update_editor(|editor, window, cx| {
15633        editor.move_left(&MoveLeft, window, cx);
15634    });
15635    cx.run_until_parked();
15636    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15637    cx.assert_editor_state("obj.aˇb");
15638    cx.update_editor(|editor, _, _| {
15639        assert_eq!(editor.context_menu_visible(), false);
15640    });
15641
15642    // Type "b" - new request
15643    cx.simulate_keystroke("b");
15644    let is_incomplete = false;
15645    handle_completion_request(
15646        "obj.<ab|>a",
15647        vec!["ab", "abc"],
15648        is_incomplete,
15649        counter.clone(),
15650        &mut cx,
15651    )
15652    .await;
15653    cx.run_until_parked();
15654    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
15655    cx.assert_editor_state("obj.abˇb");
15656    check_displayed_completions(vec!["ab", "abc"], &mut cx);
15657
15658    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
15659    cx.update_editor(|editor, window, cx| {
15660        editor.backspace(&Backspace, window, cx);
15661    });
15662    let is_incomplete = false;
15663    handle_completion_request(
15664        "obj.<a|>b",
15665        vec!["a", "ab", "abc"],
15666        is_incomplete,
15667        counter.clone(),
15668        &mut cx,
15669    )
15670    .await;
15671    cx.run_until_parked();
15672    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
15673    cx.assert_editor_state("obj.aˇb");
15674    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
15675
15676    // Backspace to delete "a" - dismisses menu.
15677    cx.update_editor(|editor, window, cx| {
15678        editor.backspace(&Backspace, window, cx);
15679    });
15680    cx.run_until_parked();
15681    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
15682    cx.assert_editor_state("obj.ˇb");
15683    cx.update_editor(|editor, _, _| {
15684        assert_eq!(editor.context_menu_visible(), false);
15685    });
15686}
15687
15688#[gpui::test]
15689async fn test_word_completion(cx: &mut TestAppContext) {
15690    let lsp_fetch_timeout_ms = 10;
15691    init_test(cx, |language_settings| {
15692        language_settings.defaults.completions = Some(CompletionSettingsContent {
15693            words_min_length: Some(0),
15694            lsp_fetch_timeout_ms: Some(10),
15695            lsp_insert_mode: Some(LspInsertMode::Insert),
15696            ..Default::default()
15697        });
15698    });
15699
15700    let mut cx = EditorLspTestContext::new_rust(
15701        lsp::ServerCapabilities {
15702            completion_provider: Some(lsp::CompletionOptions {
15703                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15704                ..lsp::CompletionOptions::default()
15705            }),
15706            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15707            ..lsp::ServerCapabilities::default()
15708        },
15709        cx,
15710    )
15711    .await;
15712
15713    let throttle_completions = Arc::new(AtomicBool::new(false));
15714
15715    let lsp_throttle_completions = throttle_completions.clone();
15716    let _completion_requests_handler =
15717        cx.lsp
15718            .server
15719            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
15720                let lsp_throttle_completions = lsp_throttle_completions.clone();
15721                let cx = cx.clone();
15722                async move {
15723                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
15724                        cx.background_executor()
15725                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
15726                            .await;
15727                    }
15728                    Ok(Some(lsp::CompletionResponse::Array(vec![
15729                        lsp::CompletionItem {
15730                            label: "first".into(),
15731                            ..lsp::CompletionItem::default()
15732                        },
15733                        lsp::CompletionItem {
15734                            label: "last".into(),
15735                            ..lsp::CompletionItem::default()
15736                        },
15737                    ])))
15738                }
15739            });
15740
15741    cx.set_state(indoc! {"
15742        oneˇ
15743        two
15744        three
15745    "});
15746    cx.simulate_keystroke(".");
15747    cx.executor().run_until_parked();
15748    cx.condition(|editor, _| editor.context_menu_visible())
15749        .await;
15750    cx.update_editor(|editor, window, cx| {
15751        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15752        {
15753            assert_eq!(
15754                completion_menu_entries(menu),
15755                &["first", "last"],
15756                "When LSP server is fast to reply, no fallback word completions are used"
15757            );
15758        } else {
15759            panic!("expected completion menu to be open");
15760        }
15761        editor.cancel(&Cancel, window, cx);
15762    });
15763    cx.executor().run_until_parked();
15764    cx.condition(|editor, _| !editor.context_menu_visible())
15765        .await;
15766
15767    throttle_completions.store(true, atomic::Ordering::Release);
15768    cx.simulate_keystroke(".");
15769    cx.executor()
15770        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
15771    cx.executor().run_until_parked();
15772    cx.condition(|editor, _| editor.context_menu_visible())
15773        .await;
15774    cx.update_editor(|editor, _, _| {
15775        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15776        {
15777            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
15778                "When LSP server is slow, document words can be shown instead, if configured accordingly");
15779        } else {
15780            panic!("expected completion menu to be open");
15781        }
15782    });
15783}
15784
15785#[gpui::test]
15786async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
15787    init_test(cx, |language_settings| {
15788        language_settings.defaults.completions = Some(CompletionSettingsContent {
15789            words: Some(WordsCompletionMode::Enabled),
15790            words_min_length: Some(0),
15791            lsp_insert_mode: Some(LspInsertMode::Insert),
15792            ..Default::default()
15793        });
15794    });
15795
15796    let mut cx = EditorLspTestContext::new_rust(
15797        lsp::ServerCapabilities {
15798            completion_provider: Some(lsp::CompletionOptions {
15799                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15800                ..lsp::CompletionOptions::default()
15801            }),
15802            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15803            ..lsp::ServerCapabilities::default()
15804        },
15805        cx,
15806    )
15807    .await;
15808
15809    let _completion_requests_handler =
15810        cx.lsp
15811            .server
15812            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
15813                Ok(Some(lsp::CompletionResponse::Array(vec![
15814                    lsp::CompletionItem {
15815                        label: "first".into(),
15816                        ..lsp::CompletionItem::default()
15817                    },
15818                    lsp::CompletionItem {
15819                        label: "last".into(),
15820                        ..lsp::CompletionItem::default()
15821                    },
15822                ])))
15823            });
15824
15825    cx.set_state(indoc! {"ˇ
15826        first
15827        last
15828        second
15829    "});
15830    cx.simulate_keystroke(".");
15831    cx.executor().run_until_parked();
15832    cx.condition(|editor, _| editor.context_menu_visible())
15833        .await;
15834    cx.update_editor(|editor, _, _| {
15835        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15836        {
15837            assert_eq!(
15838                completion_menu_entries(menu),
15839                &["first", "last", "second"],
15840                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
15841            );
15842        } else {
15843            panic!("expected completion menu to be open");
15844        }
15845    });
15846}
15847
15848#[gpui::test]
15849async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
15850    init_test(cx, |language_settings| {
15851        language_settings.defaults.completions = Some(CompletionSettingsContent {
15852            words: Some(WordsCompletionMode::Disabled),
15853            words_min_length: Some(0),
15854            lsp_insert_mode: Some(LspInsertMode::Insert),
15855            ..Default::default()
15856        });
15857    });
15858
15859    let mut cx = EditorLspTestContext::new_rust(
15860        lsp::ServerCapabilities {
15861            completion_provider: Some(lsp::CompletionOptions {
15862                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15863                ..lsp::CompletionOptions::default()
15864            }),
15865            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15866            ..lsp::ServerCapabilities::default()
15867        },
15868        cx,
15869    )
15870    .await;
15871
15872    let _completion_requests_handler =
15873        cx.lsp
15874            .server
15875            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
15876                panic!("LSP completions should not be queried when dealing with word completions")
15877            });
15878
15879    cx.set_state(indoc! {"ˇ
15880        first
15881        last
15882        second
15883    "});
15884    cx.update_editor(|editor, window, cx| {
15885        editor.show_word_completions(&ShowWordCompletions, window, cx);
15886    });
15887    cx.executor().run_until_parked();
15888    cx.condition(|editor, _| editor.context_menu_visible())
15889        .await;
15890    cx.update_editor(|editor, _, _| {
15891        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15892        {
15893            assert_eq!(
15894                completion_menu_entries(menu),
15895                &["first", "last", "second"],
15896                "`ShowWordCompletions` action should show word completions"
15897            );
15898        } else {
15899            panic!("expected completion menu to be open");
15900        }
15901    });
15902
15903    cx.simulate_keystroke("l");
15904    cx.executor().run_until_parked();
15905    cx.condition(|editor, _| editor.context_menu_visible())
15906        .await;
15907    cx.update_editor(|editor, _, _| {
15908        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15909        {
15910            assert_eq!(
15911                completion_menu_entries(menu),
15912                &["last"],
15913                "After showing word completions, further editing should filter them and not query the LSP"
15914            );
15915        } else {
15916            panic!("expected completion menu to be open");
15917        }
15918    });
15919}
15920
15921#[gpui::test]
15922async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
15923    init_test(cx, |language_settings| {
15924        language_settings.defaults.completions = Some(CompletionSettingsContent {
15925            words_min_length: Some(0),
15926            lsp: Some(false),
15927            lsp_insert_mode: Some(LspInsertMode::Insert),
15928            ..Default::default()
15929        });
15930    });
15931
15932    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15933
15934    cx.set_state(indoc! {"ˇ
15935        0_usize
15936        let
15937        33
15938        4.5f32
15939    "});
15940    cx.update_editor(|editor, window, cx| {
15941        editor.show_completions(&ShowCompletions, window, cx);
15942    });
15943    cx.executor().run_until_parked();
15944    cx.condition(|editor, _| editor.context_menu_visible())
15945        .await;
15946    cx.update_editor(|editor, window, cx| {
15947        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15948        {
15949            assert_eq!(
15950                completion_menu_entries(menu),
15951                &["let"],
15952                "With no digits in the completion query, no digits should be in the word completions"
15953            );
15954        } else {
15955            panic!("expected completion menu to be open");
15956        }
15957        editor.cancel(&Cancel, window, cx);
15958    });
15959
15960    cx.set_state(indoc! {"15961        0_usize
15962        let
15963        3
15964        33.35f32
15965    "});
15966    cx.update_editor(|editor, window, cx| {
15967        editor.show_completions(&ShowCompletions, window, cx);
15968    });
15969    cx.executor().run_until_parked();
15970    cx.condition(|editor, _| editor.context_menu_visible())
15971        .await;
15972    cx.update_editor(|editor, _, _| {
15973        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15974        {
15975            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
15976                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
15977        } else {
15978            panic!("expected completion menu to be open");
15979        }
15980    });
15981}
15982
15983#[gpui::test]
15984async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
15985    init_test(cx, |language_settings| {
15986        language_settings.defaults.completions = Some(CompletionSettingsContent {
15987            words: Some(WordsCompletionMode::Enabled),
15988            words_min_length: Some(3),
15989            lsp_insert_mode: Some(LspInsertMode::Insert),
15990            ..Default::default()
15991        });
15992    });
15993
15994    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15995    cx.set_state(indoc! {"ˇ
15996        wow
15997        wowen
15998        wowser
15999    "});
16000    cx.simulate_keystroke("w");
16001    cx.executor().run_until_parked();
16002    cx.update_editor(|editor, _, _| {
16003        if editor.context_menu.borrow_mut().is_some() {
16004            panic!(
16005                "expected completion menu to be hidden, as words completion threshold is not met"
16006            );
16007        }
16008    });
16009
16010    cx.update_editor(|editor, window, cx| {
16011        editor.show_word_completions(&ShowWordCompletions, window, cx);
16012    });
16013    cx.executor().run_until_parked();
16014    cx.update_editor(|editor, window, cx| {
16015        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16016        {
16017            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");
16018        } else {
16019            panic!("expected completion menu to be open after the word completions are called with an action");
16020        }
16021
16022        editor.cancel(&Cancel, window, cx);
16023    });
16024    cx.update_editor(|editor, _, _| {
16025        if editor.context_menu.borrow_mut().is_some() {
16026            panic!("expected completion menu to be hidden after canceling");
16027        }
16028    });
16029
16030    cx.simulate_keystroke("o");
16031    cx.executor().run_until_parked();
16032    cx.update_editor(|editor, _, _| {
16033        if editor.context_menu.borrow_mut().is_some() {
16034            panic!(
16035                "expected completion menu to be hidden, as words completion threshold is not met still"
16036            );
16037        }
16038    });
16039
16040    cx.simulate_keystroke("w");
16041    cx.executor().run_until_parked();
16042    cx.update_editor(|editor, _, _| {
16043        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16044        {
16045            assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
16046        } else {
16047            panic!("expected completion menu to be open after the word completions threshold is met");
16048        }
16049    });
16050}
16051
16052#[gpui::test]
16053async fn test_word_completions_disabled(cx: &mut TestAppContext) {
16054    init_test(cx, |language_settings| {
16055        language_settings.defaults.completions = Some(CompletionSettingsContent {
16056            words: Some(WordsCompletionMode::Enabled),
16057            words_min_length: Some(0),
16058            lsp_insert_mode: Some(LspInsertMode::Insert),
16059            ..Default::default()
16060        });
16061    });
16062
16063    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16064    cx.update_editor(|editor, _, _| {
16065        editor.disable_word_completions();
16066    });
16067    cx.set_state(indoc! {"ˇ
16068        wow
16069        wowen
16070        wowser
16071    "});
16072    cx.simulate_keystroke("w");
16073    cx.executor().run_until_parked();
16074    cx.update_editor(|editor, _, _| {
16075        if editor.context_menu.borrow_mut().is_some() {
16076            panic!(
16077                "expected completion menu to be hidden, as words completion are disabled for this editor"
16078            );
16079        }
16080    });
16081
16082    cx.update_editor(|editor, window, cx| {
16083        editor.show_word_completions(&ShowWordCompletions, window, cx);
16084    });
16085    cx.executor().run_until_parked();
16086    cx.update_editor(|editor, _, _| {
16087        if editor.context_menu.borrow_mut().is_some() {
16088            panic!(
16089                "expected completion menu to be hidden even if called for explicitly, as words completion are disabled for this editor"
16090            );
16091        }
16092    });
16093}
16094
16095#[gpui::test]
16096async fn test_word_completions_disabled_with_no_provider(cx: &mut TestAppContext) {
16097    init_test(cx, |language_settings| {
16098        language_settings.defaults.completions = Some(CompletionSettingsContent {
16099            words: Some(WordsCompletionMode::Disabled),
16100            words_min_length: Some(0),
16101            lsp_insert_mode: Some(LspInsertMode::Insert),
16102            ..Default::default()
16103        });
16104    });
16105
16106    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16107    cx.update_editor(|editor, _, _| {
16108        editor.set_completion_provider(None);
16109    });
16110    cx.set_state(indoc! {"ˇ
16111        wow
16112        wowen
16113        wowser
16114    "});
16115    cx.simulate_keystroke("w");
16116    cx.executor().run_until_parked();
16117    cx.update_editor(|editor, _, _| {
16118        if editor.context_menu.borrow_mut().is_some() {
16119            panic!("expected completion menu to be hidden, as disabled in settings");
16120        }
16121    });
16122}
16123
16124fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
16125    let position = || lsp::Position {
16126        line: params.text_document_position.position.line,
16127        character: params.text_document_position.position.character,
16128    };
16129    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16130        range: lsp::Range {
16131            start: position(),
16132            end: position(),
16133        },
16134        new_text: text.to_string(),
16135    }))
16136}
16137
16138#[gpui::test]
16139async fn test_multiline_completion(cx: &mut TestAppContext) {
16140    init_test(cx, |_| {});
16141
16142    let fs = FakeFs::new(cx.executor());
16143    fs.insert_tree(
16144        path!("/a"),
16145        json!({
16146            "main.ts": "a",
16147        }),
16148    )
16149    .await;
16150
16151    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16152    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
16153    let typescript_language = Arc::new(Language::new(
16154        LanguageConfig {
16155            name: "TypeScript".into(),
16156            matcher: LanguageMatcher {
16157                path_suffixes: vec!["ts".to_string()],
16158                ..LanguageMatcher::default()
16159            },
16160            line_comments: vec!["// ".into()],
16161            ..LanguageConfig::default()
16162        },
16163        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
16164    ));
16165    language_registry.add(typescript_language.clone());
16166    let mut fake_servers = language_registry.register_fake_lsp(
16167        "TypeScript",
16168        FakeLspAdapter {
16169            capabilities: lsp::ServerCapabilities {
16170                completion_provider: Some(lsp::CompletionOptions {
16171                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
16172                    ..lsp::CompletionOptions::default()
16173                }),
16174                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
16175                ..lsp::ServerCapabilities::default()
16176            },
16177            // Emulate vtsls label generation
16178            label_for_completion: Some(Box::new(|item, _| {
16179                let text = if let Some(description) = item
16180                    .label_details
16181                    .as_ref()
16182                    .and_then(|label_details| label_details.description.as_ref())
16183                {
16184                    format!("{} {}", item.label, description)
16185                } else if let Some(detail) = &item.detail {
16186                    format!("{} {}", item.label, detail)
16187                } else {
16188                    item.label.clone()
16189                };
16190                Some(language::CodeLabel::plain(text, None))
16191            })),
16192            ..FakeLspAdapter::default()
16193        },
16194    );
16195    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16196    let cx = &mut VisualTestContext::from_window(*workspace, cx);
16197    let worktree_id = workspace
16198        .update(cx, |workspace, _window, cx| {
16199            workspace.project().update(cx, |project, cx| {
16200                project.worktrees(cx).next().unwrap().read(cx).id()
16201            })
16202        })
16203        .unwrap();
16204    let _buffer = project
16205        .update(cx, |project, cx| {
16206            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
16207        })
16208        .await
16209        .unwrap();
16210    let editor = workspace
16211        .update(cx, |workspace, window, cx| {
16212            workspace.open_path((worktree_id, rel_path("main.ts")), None, true, window, cx)
16213        })
16214        .unwrap()
16215        .await
16216        .unwrap()
16217        .downcast::<Editor>()
16218        .unwrap();
16219    let fake_server = fake_servers.next().await.unwrap();
16220    cx.run_until_parked();
16221
16222    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
16223    let multiline_label_2 = "a\nb\nc\n";
16224    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
16225    let multiline_description = "d\ne\nf\n";
16226    let multiline_detail_2 = "g\nh\ni\n";
16227
16228    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
16229        move |params, _| async move {
16230            Ok(Some(lsp::CompletionResponse::Array(vec![
16231                lsp::CompletionItem {
16232                    label: multiline_label.to_string(),
16233                    text_edit: gen_text_edit(&params, "new_text_1"),
16234                    ..lsp::CompletionItem::default()
16235                },
16236                lsp::CompletionItem {
16237                    label: "single line label 1".to_string(),
16238                    detail: Some(multiline_detail.to_string()),
16239                    text_edit: gen_text_edit(&params, "new_text_2"),
16240                    ..lsp::CompletionItem::default()
16241                },
16242                lsp::CompletionItem {
16243                    label: "single line label 2".to_string(),
16244                    label_details: Some(lsp::CompletionItemLabelDetails {
16245                        description: Some(multiline_description.to_string()),
16246                        detail: None,
16247                    }),
16248                    text_edit: gen_text_edit(&params, "new_text_2"),
16249                    ..lsp::CompletionItem::default()
16250                },
16251                lsp::CompletionItem {
16252                    label: multiline_label_2.to_string(),
16253                    detail: Some(multiline_detail_2.to_string()),
16254                    text_edit: gen_text_edit(&params, "new_text_3"),
16255                    ..lsp::CompletionItem::default()
16256                },
16257                lsp::CompletionItem {
16258                    label: "Label with many     spaces and \t but without newlines".to_string(),
16259                    detail: Some(
16260                        "Details with many     spaces and \t but without newlines".to_string(),
16261                    ),
16262                    text_edit: gen_text_edit(&params, "new_text_4"),
16263                    ..lsp::CompletionItem::default()
16264                },
16265            ])))
16266        },
16267    );
16268
16269    editor.update_in(cx, |editor, window, cx| {
16270        cx.focus_self(window);
16271        editor.move_to_end(&MoveToEnd, window, cx);
16272        editor.handle_input(".", window, cx);
16273    });
16274    cx.run_until_parked();
16275    completion_handle.next().await.unwrap();
16276
16277    editor.update(cx, |editor, _| {
16278        assert!(editor.context_menu_visible());
16279        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16280        {
16281            let completion_labels = menu
16282                .completions
16283                .borrow()
16284                .iter()
16285                .map(|c| c.label.text.clone())
16286                .collect::<Vec<_>>();
16287            assert_eq!(
16288                completion_labels,
16289                &[
16290                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
16291                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
16292                    "single line label 2 d e f ",
16293                    "a b c g h i ",
16294                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
16295                ],
16296                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
16297            );
16298
16299            for completion in menu
16300                .completions
16301                .borrow()
16302                .iter() {
16303                    assert_eq!(
16304                        completion.label.filter_range,
16305                        0..completion.label.text.len(),
16306                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
16307                    );
16308                }
16309        } else {
16310            panic!("expected completion menu to be open");
16311        }
16312    });
16313}
16314
16315#[gpui::test]
16316async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
16317    init_test(cx, |_| {});
16318    let mut cx = EditorLspTestContext::new_rust(
16319        lsp::ServerCapabilities {
16320            completion_provider: Some(lsp::CompletionOptions {
16321                trigger_characters: Some(vec![".".to_string()]),
16322                ..Default::default()
16323            }),
16324            ..Default::default()
16325        },
16326        cx,
16327    )
16328    .await;
16329    cx.lsp
16330        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
16331            Ok(Some(lsp::CompletionResponse::Array(vec![
16332                lsp::CompletionItem {
16333                    label: "first".into(),
16334                    ..Default::default()
16335                },
16336                lsp::CompletionItem {
16337                    label: "last".into(),
16338                    ..Default::default()
16339                },
16340            ])))
16341        });
16342    cx.set_state("variableˇ");
16343    cx.simulate_keystroke(".");
16344    cx.executor().run_until_parked();
16345
16346    cx.update_editor(|editor, _, _| {
16347        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16348        {
16349            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
16350        } else {
16351            panic!("expected completion menu to be open");
16352        }
16353    });
16354
16355    cx.update_editor(|editor, window, cx| {
16356        editor.move_page_down(&MovePageDown::default(), window, cx);
16357        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16358        {
16359            assert!(
16360                menu.selected_item == 1,
16361                "expected PageDown to select the last item from the context menu"
16362            );
16363        } else {
16364            panic!("expected completion menu to stay open after PageDown");
16365        }
16366    });
16367
16368    cx.update_editor(|editor, window, cx| {
16369        editor.move_page_up(&MovePageUp::default(), window, cx);
16370        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16371        {
16372            assert!(
16373                menu.selected_item == 0,
16374                "expected PageUp to select the first item from the context menu"
16375            );
16376        } else {
16377            panic!("expected completion menu to stay open after PageUp");
16378        }
16379    });
16380}
16381
16382#[gpui::test]
16383async fn test_as_is_completions(cx: &mut TestAppContext) {
16384    init_test(cx, |_| {});
16385    let mut cx = EditorLspTestContext::new_rust(
16386        lsp::ServerCapabilities {
16387            completion_provider: Some(lsp::CompletionOptions {
16388                ..Default::default()
16389            }),
16390            ..Default::default()
16391        },
16392        cx,
16393    )
16394    .await;
16395    cx.lsp
16396        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
16397            Ok(Some(lsp::CompletionResponse::Array(vec![
16398                lsp::CompletionItem {
16399                    label: "unsafe".into(),
16400                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16401                        range: lsp::Range {
16402                            start: lsp::Position {
16403                                line: 1,
16404                                character: 2,
16405                            },
16406                            end: lsp::Position {
16407                                line: 1,
16408                                character: 3,
16409                            },
16410                        },
16411                        new_text: "unsafe".to_string(),
16412                    })),
16413                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
16414                    ..Default::default()
16415                },
16416            ])))
16417        });
16418    cx.set_state("fn a() {}\n");
16419    cx.executor().run_until_parked();
16420    cx.update_editor(|editor, window, cx| {
16421        editor.trigger_completion_on_input("n", true, window, cx)
16422    });
16423    cx.executor().run_until_parked();
16424
16425    cx.update_editor(|editor, window, cx| {
16426        editor.confirm_completion(&Default::default(), window, cx)
16427    });
16428    cx.executor().run_until_parked();
16429    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
16430}
16431
16432#[gpui::test]
16433async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
16434    init_test(cx, |_| {});
16435    let language =
16436        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
16437    let mut cx = EditorLspTestContext::new(
16438        language,
16439        lsp::ServerCapabilities {
16440            completion_provider: Some(lsp::CompletionOptions {
16441                ..lsp::CompletionOptions::default()
16442            }),
16443            ..lsp::ServerCapabilities::default()
16444        },
16445        cx,
16446    )
16447    .await;
16448
16449    cx.set_state(
16450        "#ifndef BAR_H
16451#define BAR_H
16452
16453#include <stdbool.h>
16454
16455int fn_branch(bool do_branch1, bool do_branch2);
16456
16457#endif // BAR_H
16458ˇ",
16459    );
16460    cx.executor().run_until_parked();
16461    cx.update_editor(|editor, window, cx| {
16462        editor.handle_input("#", window, cx);
16463    });
16464    cx.executor().run_until_parked();
16465    cx.update_editor(|editor, window, cx| {
16466        editor.handle_input("i", window, cx);
16467    });
16468    cx.executor().run_until_parked();
16469    cx.update_editor(|editor, window, cx| {
16470        editor.handle_input("n", window, cx);
16471    });
16472    cx.executor().run_until_parked();
16473    cx.assert_editor_state(
16474        "#ifndef BAR_H
16475#define BAR_H
16476
16477#include <stdbool.h>
16478
16479int fn_branch(bool do_branch1, bool do_branch2);
16480
16481#endif // BAR_H
16482#inˇ",
16483    );
16484
16485    cx.lsp
16486        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
16487            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
16488                is_incomplete: false,
16489                item_defaults: None,
16490                items: vec![lsp::CompletionItem {
16491                    kind: Some(lsp::CompletionItemKind::SNIPPET),
16492                    label_details: Some(lsp::CompletionItemLabelDetails {
16493                        detail: Some("header".to_string()),
16494                        description: None,
16495                    }),
16496                    label: " include".to_string(),
16497                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16498                        range: lsp::Range {
16499                            start: lsp::Position {
16500                                line: 8,
16501                                character: 1,
16502                            },
16503                            end: lsp::Position {
16504                                line: 8,
16505                                character: 1,
16506                            },
16507                        },
16508                        new_text: "include \"$0\"".to_string(),
16509                    })),
16510                    sort_text: Some("40b67681include".to_string()),
16511                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
16512                    filter_text: Some("include".to_string()),
16513                    insert_text: Some("include \"$0\"".to_string()),
16514                    ..lsp::CompletionItem::default()
16515                }],
16516            })))
16517        });
16518    cx.update_editor(|editor, window, cx| {
16519        editor.show_completions(&ShowCompletions, window, cx);
16520    });
16521    cx.executor().run_until_parked();
16522    cx.update_editor(|editor, window, cx| {
16523        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
16524    });
16525    cx.executor().run_until_parked();
16526    cx.assert_editor_state(
16527        "#ifndef BAR_H
16528#define BAR_H
16529
16530#include <stdbool.h>
16531
16532int fn_branch(bool do_branch1, bool do_branch2);
16533
16534#endif // BAR_H
16535#include \"ˇ\"",
16536    );
16537
16538    cx.lsp
16539        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
16540            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
16541                is_incomplete: true,
16542                item_defaults: None,
16543                items: vec![lsp::CompletionItem {
16544                    kind: Some(lsp::CompletionItemKind::FILE),
16545                    label: "AGL/".to_string(),
16546                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16547                        range: lsp::Range {
16548                            start: lsp::Position {
16549                                line: 8,
16550                                character: 10,
16551                            },
16552                            end: lsp::Position {
16553                                line: 8,
16554                                character: 11,
16555                            },
16556                        },
16557                        new_text: "AGL/".to_string(),
16558                    })),
16559                    sort_text: Some("40b67681AGL/".to_string()),
16560                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
16561                    filter_text: Some("AGL/".to_string()),
16562                    insert_text: Some("AGL/".to_string()),
16563                    ..lsp::CompletionItem::default()
16564                }],
16565            })))
16566        });
16567    cx.update_editor(|editor, window, cx| {
16568        editor.show_completions(&ShowCompletions, window, cx);
16569    });
16570    cx.executor().run_until_parked();
16571    cx.update_editor(|editor, window, cx| {
16572        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
16573    });
16574    cx.executor().run_until_parked();
16575    cx.assert_editor_state(
16576        r##"#ifndef BAR_H
16577#define BAR_H
16578
16579#include <stdbool.h>
16580
16581int fn_branch(bool do_branch1, bool do_branch2);
16582
16583#endif // BAR_H
16584#include "AGL/ˇ"##,
16585    );
16586
16587    cx.update_editor(|editor, window, cx| {
16588        editor.handle_input("\"", window, cx);
16589    });
16590    cx.executor().run_until_parked();
16591    cx.assert_editor_state(
16592        r##"#ifndef BAR_H
16593#define BAR_H
16594
16595#include <stdbool.h>
16596
16597int fn_branch(bool do_branch1, bool do_branch2);
16598
16599#endif // BAR_H
16600#include "AGL/"ˇ"##,
16601    );
16602}
16603
16604#[gpui::test]
16605async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
16606    init_test(cx, |_| {});
16607
16608    let mut cx = EditorLspTestContext::new_rust(
16609        lsp::ServerCapabilities {
16610            completion_provider: Some(lsp::CompletionOptions {
16611                trigger_characters: Some(vec![".".to_string()]),
16612                resolve_provider: Some(true),
16613                ..Default::default()
16614            }),
16615            ..Default::default()
16616        },
16617        cx,
16618    )
16619    .await;
16620
16621    cx.set_state("fn main() { let a = 2ˇ; }");
16622    cx.simulate_keystroke(".");
16623    let completion_item = lsp::CompletionItem {
16624        label: "Some".into(),
16625        kind: Some(lsp::CompletionItemKind::SNIPPET),
16626        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
16627        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
16628            kind: lsp::MarkupKind::Markdown,
16629            value: "```rust\nSome(2)\n```".to_string(),
16630        })),
16631        deprecated: Some(false),
16632        sort_text: Some("Some".to_string()),
16633        filter_text: Some("Some".to_string()),
16634        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
16635        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16636            range: lsp::Range {
16637                start: lsp::Position {
16638                    line: 0,
16639                    character: 22,
16640                },
16641                end: lsp::Position {
16642                    line: 0,
16643                    character: 22,
16644                },
16645            },
16646            new_text: "Some(2)".to_string(),
16647        })),
16648        additional_text_edits: Some(vec![lsp::TextEdit {
16649            range: lsp::Range {
16650                start: lsp::Position {
16651                    line: 0,
16652                    character: 20,
16653                },
16654                end: lsp::Position {
16655                    line: 0,
16656                    character: 22,
16657                },
16658            },
16659            new_text: "".to_string(),
16660        }]),
16661        ..Default::default()
16662    };
16663
16664    let closure_completion_item = completion_item.clone();
16665    let counter = Arc::new(AtomicUsize::new(0));
16666    let counter_clone = counter.clone();
16667    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
16668        let task_completion_item = closure_completion_item.clone();
16669        counter_clone.fetch_add(1, atomic::Ordering::Release);
16670        async move {
16671            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
16672                is_incomplete: true,
16673                item_defaults: None,
16674                items: vec![task_completion_item],
16675            })))
16676        }
16677    });
16678
16679    cx.condition(|editor, _| editor.context_menu_visible())
16680        .await;
16681    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
16682    assert!(request.next().await.is_some());
16683    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
16684
16685    cx.simulate_keystrokes("S o m");
16686    cx.condition(|editor, _| editor.context_menu_visible())
16687        .await;
16688    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
16689    assert!(request.next().await.is_some());
16690    assert!(request.next().await.is_some());
16691    assert!(request.next().await.is_some());
16692    request.close();
16693    assert!(request.next().await.is_none());
16694    assert_eq!(
16695        counter.load(atomic::Ordering::Acquire),
16696        4,
16697        "With the completions menu open, only one LSP request should happen per input"
16698    );
16699}
16700
16701#[gpui::test]
16702async fn test_toggle_comment(cx: &mut TestAppContext) {
16703    init_test(cx, |_| {});
16704    let mut cx = EditorTestContext::new(cx).await;
16705    let language = Arc::new(Language::new(
16706        LanguageConfig {
16707            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
16708            ..Default::default()
16709        },
16710        Some(tree_sitter_rust::LANGUAGE.into()),
16711    ));
16712    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
16713
16714    // If multiple selections intersect a line, the line is only toggled once.
16715    cx.set_state(indoc! {"
16716        fn a() {
16717            «//b();
16718            ˇ»// «c();
16719            //ˇ»  d();
16720        }
16721    "});
16722
16723    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16724
16725    cx.assert_editor_state(indoc! {"
16726        fn a() {
16727            «b();
16728            ˇ»«c();
16729            ˇ» d();
16730        }
16731    "});
16732
16733    // The comment prefix is inserted at the same column for every line in a
16734    // selection.
16735    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16736
16737    cx.assert_editor_state(indoc! {"
16738        fn a() {
16739            // «b();
16740            ˇ»// «c();
16741            ˇ» // d();
16742        }
16743    "});
16744
16745    // If a selection ends at the beginning of a line, that line is not toggled.
16746    cx.set_selections_state(indoc! {"
16747        fn a() {
16748            // b();
16749            «// c();
16750        ˇ»     // d();
16751        }
16752    "});
16753
16754    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16755
16756    cx.assert_editor_state(indoc! {"
16757        fn a() {
16758            // b();
16759            «c();
16760        ˇ»     // d();
16761        }
16762    "});
16763
16764    // If a selection span a single line and is empty, the line is toggled.
16765    cx.set_state(indoc! {"
16766        fn a() {
16767            a();
16768            b();
16769        ˇ
16770        }
16771    "});
16772
16773    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16774
16775    cx.assert_editor_state(indoc! {"
16776        fn a() {
16777            a();
16778            b();
16779        //•ˇ
16780        }
16781    "});
16782
16783    // If a selection span multiple lines, empty lines are not toggled.
16784    cx.set_state(indoc! {"
16785        fn a() {
16786            «a();
16787
16788            c();ˇ»
16789        }
16790    "});
16791
16792    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16793
16794    cx.assert_editor_state(indoc! {"
16795        fn a() {
16796            // «a();
16797
16798            // c();ˇ»
16799        }
16800    "});
16801
16802    // If a selection includes multiple comment prefixes, all lines are uncommented.
16803    cx.set_state(indoc! {"
16804        fn a() {
16805            «// a();
16806            /// b();
16807            //! c();ˇ»
16808        }
16809    "});
16810
16811    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16812
16813    cx.assert_editor_state(indoc! {"
16814        fn a() {
16815            «a();
16816            b();
16817            c();ˇ»
16818        }
16819    "});
16820}
16821
16822#[gpui::test]
16823async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
16824    init_test(cx, |_| {});
16825    let mut cx = EditorTestContext::new(cx).await;
16826    let language = Arc::new(Language::new(
16827        LanguageConfig {
16828            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
16829            ..Default::default()
16830        },
16831        Some(tree_sitter_rust::LANGUAGE.into()),
16832    ));
16833    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
16834
16835    let toggle_comments = &ToggleComments {
16836        advance_downwards: false,
16837        ignore_indent: true,
16838    };
16839
16840    // If multiple selections intersect a line, the line is only toggled once.
16841    cx.set_state(indoc! {"
16842        fn a() {
16843        //    «b();
16844        //    c();
16845        //    ˇ» d();
16846        }
16847    "});
16848
16849    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16850
16851    cx.assert_editor_state(indoc! {"
16852        fn a() {
16853            «b();
16854            c();
16855            ˇ» d();
16856        }
16857    "});
16858
16859    // The comment prefix is inserted at the beginning of each line
16860    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16861
16862    cx.assert_editor_state(indoc! {"
16863        fn a() {
16864        //    «b();
16865        //    c();
16866        //    ˇ» d();
16867        }
16868    "});
16869
16870    // If a selection ends at the beginning of a line, that line is not toggled.
16871    cx.set_selections_state(indoc! {"
16872        fn a() {
16873        //    b();
16874        //    «c();
16875        ˇ»//     d();
16876        }
16877    "});
16878
16879    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16880
16881    cx.assert_editor_state(indoc! {"
16882        fn a() {
16883        //    b();
16884            «c();
16885        ˇ»//     d();
16886        }
16887    "});
16888
16889    // If a selection span a single line and is empty, the line is toggled.
16890    cx.set_state(indoc! {"
16891        fn a() {
16892            a();
16893            b();
16894        ˇ
16895        }
16896    "});
16897
16898    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16899
16900    cx.assert_editor_state(indoc! {"
16901        fn a() {
16902            a();
16903            b();
16904        //ˇ
16905        }
16906    "});
16907
16908    // If a selection span multiple lines, empty lines are not toggled.
16909    cx.set_state(indoc! {"
16910        fn a() {
16911            «a();
16912
16913            c();ˇ»
16914        }
16915    "});
16916
16917    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16918
16919    cx.assert_editor_state(indoc! {"
16920        fn a() {
16921        //    «a();
16922
16923        //    c();ˇ»
16924        }
16925    "});
16926
16927    // If a selection includes multiple comment prefixes, all lines are uncommented.
16928    cx.set_state(indoc! {"
16929        fn a() {
16930        //    «a();
16931        ///    b();
16932        //!    c();ˇ»
16933        }
16934    "});
16935
16936    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16937
16938    cx.assert_editor_state(indoc! {"
16939        fn a() {
16940            «a();
16941            b();
16942            c();ˇ»
16943        }
16944    "});
16945}
16946
16947#[gpui::test]
16948async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
16949    init_test(cx, |_| {});
16950
16951    let language = Arc::new(Language::new(
16952        LanguageConfig {
16953            line_comments: vec!["// ".into()],
16954            ..Default::default()
16955        },
16956        Some(tree_sitter_rust::LANGUAGE.into()),
16957    ));
16958
16959    let mut cx = EditorTestContext::new(cx).await;
16960
16961    cx.language_registry().add(language.clone());
16962    cx.update_buffer(|buffer, cx| {
16963        buffer.set_language(Some(language), cx);
16964    });
16965
16966    let toggle_comments = &ToggleComments {
16967        advance_downwards: true,
16968        ignore_indent: false,
16969    };
16970
16971    // Single cursor on one line -> advance
16972    // Cursor moves horizontally 3 characters as well on non-blank line
16973    cx.set_state(indoc!(
16974        "fn a() {
16975             ˇdog();
16976             cat();
16977        }"
16978    ));
16979    cx.update_editor(|editor, window, cx| {
16980        editor.toggle_comments(toggle_comments, window, cx);
16981    });
16982    cx.assert_editor_state(indoc!(
16983        "fn a() {
16984             // dog();
16985             catˇ();
16986        }"
16987    ));
16988
16989    // Single selection on one line -> don't advance
16990    cx.set_state(indoc!(
16991        "fn a() {
16992             «dog()ˇ»;
16993             cat();
16994        }"
16995    ));
16996    cx.update_editor(|editor, window, cx| {
16997        editor.toggle_comments(toggle_comments, window, cx);
16998    });
16999    cx.assert_editor_state(indoc!(
17000        "fn a() {
17001             // «dog()ˇ»;
17002             cat();
17003        }"
17004    ));
17005
17006    // Multiple cursors on one line -> advance
17007    cx.set_state(indoc!(
17008        "fn a() {
17009             ˇdˇog();
17010             cat();
17011        }"
17012    ));
17013    cx.update_editor(|editor, window, cx| {
17014        editor.toggle_comments(toggle_comments, window, cx);
17015    });
17016    cx.assert_editor_state(indoc!(
17017        "fn a() {
17018             // dog();
17019             catˇ(ˇ);
17020        }"
17021    ));
17022
17023    // Multiple cursors on one line, with selection -> don't advance
17024    cx.set_state(indoc!(
17025        "fn a() {
17026             ˇdˇog«()ˇ»;
17027             cat();
17028        }"
17029    ));
17030    cx.update_editor(|editor, window, cx| {
17031        editor.toggle_comments(toggle_comments, window, cx);
17032    });
17033    cx.assert_editor_state(indoc!(
17034        "fn a() {
17035             // ˇdˇog«()ˇ»;
17036             cat();
17037        }"
17038    ));
17039
17040    // Single cursor on one line -> advance
17041    // Cursor moves to column 0 on blank line
17042    cx.set_state(indoc!(
17043        "fn a() {
17044             ˇdog();
17045
17046             cat();
17047        }"
17048    ));
17049    cx.update_editor(|editor, window, cx| {
17050        editor.toggle_comments(toggle_comments, window, cx);
17051    });
17052    cx.assert_editor_state(indoc!(
17053        "fn a() {
17054             // dog();
17055        ˇ
17056             cat();
17057        }"
17058    ));
17059
17060    // Single cursor on one line -> advance
17061    // Cursor starts and ends at column 0
17062    cx.set_state(indoc!(
17063        "fn a() {
17064         ˇ    dog();
17065             cat();
17066        }"
17067    ));
17068    cx.update_editor(|editor, window, cx| {
17069        editor.toggle_comments(toggle_comments, window, cx);
17070    });
17071    cx.assert_editor_state(indoc!(
17072        "fn a() {
17073             // dog();
17074         ˇ    cat();
17075        }"
17076    ));
17077}
17078
17079#[gpui::test]
17080async fn test_toggle_block_comment(cx: &mut TestAppContext) {
17081    init_test(cx, |_| {});
17082
17083    let mut cx = EditorTestContext::new(cx).await;
17084
17085    let html_language = Arc::new(
17086        Language::new(
17087            LanguageConfig {
17088                name: "HTML".into(),
17089                block_comment: Some(BlockCommentConfig {
17090                    start: "<!-- ".into(),
17091                    prefix: "".into(),
17092                    end: " -->".into(),
17093                    tab_size: 0,
17094                }),
17095                ..Default::default()
17096            },
17097            Some(tree_sitter_html::LANGUAGE.into()),
17098        )
17099        .with_injection_query(
17100            r#"
17101            (script_element
17102                (raw_text) @injection.content
17103                (#set! injection.language "javascript"))
17104            "#,
17105        )
17106        .unwrap(),
17107    );
17108
17109    let javascript_language = Arc::new(Language::new(
17110        LanguageConfig {
17111            name: "JavaScript".into(),
17112            line_comments: vec!["// ".into()],
17113            ..Default::default()
17114        },
17115        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
17116    ));
17117
17118    cx.language_registry().add(html_language.clone());
17119    cx.language_registry().add(javascript_language);
17120    cx.update_buffer(|buffer, cx| {
17121        buffer.set_language(Some(html_language), cx);
17122    });
17123
17124    // Toggle comments for empty selections
17125    cx.set_state(
17126        &r#"
17127            <p>A</p>ˇ
17128            <p>B</p>ˇ
17129            <p>C</p>ˇ
17130        "#
17131        .unindent(),
17132    );
17133    cx.update_editor(|editor, window, cx| {
17134        editor.toggle_comments(&ToggleComments::default(), window, cx)
17135    });
17136    cx.assert_editor_state(
17137        &r#"
17138            <!-- <p>A</p>ˇ -->
17139            <!-- <p>B</p>ˇ -->
17140            <!-- <p>C</p>ˇ -->
17141        "#
17142        .unindent(),
17143    );
17144    cx.update_editor(|editor, window, cx| {
17145        editor.toggle_comments(&ToggleComments::default(), window, cx)
17146    });
17147    cx.assert_editor_state(
17148        &r#"
17149            <p>A</p>ˇ
17150            <p>B</p>ˇ
17151            <p>C</p>ˇ
17152        "#
17153        .unindent(),
17154    );
17155
17156    // Toggle comments for mixture of empty and non-empty selections, where
17157    // multiple selections occupy a given line.
17158    cx.set_state(
17159        &r#"
17160            <p>A«</p>
17161            <p>ˇ»B</p>ˇ
17162            <p>C«</p>
17163            <p>ˇ»D</p>ˇ
17164        "#
17165        .unindent(),
17166    );
17167
17168    cx.update_editor(|editor, window, cx| {
17169        editor.toggle_comments(&ToggleComments::default(), window, cx)
17170    });
17171    cx.assert_editor_state(
17172        &r#"
17173            <!-- <p>A«</p>
17174            <p>ˇ»B</p>ˇ -->
17175            <!-- <p>C«</p>
17176            <p>ˇ»D</p>ˇ -->
17177        "#
17178        .unindent(),
17179    );
17180    cx.update_editor(|editor, window, cx| {
17181        editor.toggle_comments(&ToggleComments::default(), window, cx)
17182    });
17183    cx.assert_editor_state(
17184        &r#"
17185            <p>A«</p>
17186            <p>ˇ»B</p>ˇ
17187            <p>C«</p>
17188            <p>ˇ»D</p>ˇ
17189        "#
17190        .unindent(),
17191    );
17192
17193    // Toggle comments when different languages are active for different
17194    // selections.
17195    cx.set_state(
17196        &r#"
17197            ˇ<script>
17198                ˇvar x = new Y();
17199            ˇ</script>
17200        "#
17201        .unindent(),
17202    );
17203    cx.executor().run_until_parked();
17204    cx.update_editor(|editor, window, cx| {
17205        editor.toggle_comments(&ToggleComments::default(), window, cx)
17206    });
17207    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
17208    // Uncommenting and commenting from this position brings in even more wrong artifacts.
17209    cx.assert_editor_state(
17210        &r#"
17211            <!-- ˇ<script> -->
17212                // ˇvar x = new Y();
17213            <!-- ˇ</script> -->
17214        "#
17215        .unindent(),
17216    );
17217}
17218
17219#[gpui::test]
17220fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
17221    init_test(cx, |_| {});
17222
17223    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
17224    let multibuffer = cx.new(|cx| {
17225        let mut multibuffer = MultiBuffer::new(ReadWrite);
17226        multibuffer.push_excerpts(
17227            buffer.clone(),
17228            [
17229                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
17230                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
17231            ],
17232            cx,
17233        );
17234        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
17235        multibuffer
17236    });
17237
17238    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
17239    editor.update_in(cx, |editor, window, cx| {
17240        assert_eq!(editor.text(cx), "aaaa\nbbbb");
17241        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17242            s.select_ranges([
17243                Point::new(0, 0)..Point::new(0, 0),
17244                Point::new(1, 0)..Point::new(1, 0),
17245            ])
17246        });
17247
17248        editor.handle_input("X", window, cx);
17249        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
17250        assert_eq!(
17251            editor.selections.ranges(&editor.display_snapshot(cx)),
17252            [
17253                Point::new(0, 1)..Point::new(0, 1),
17254                Point::new(1, 1)..Point::new(1, 1),
17255            ]
17256        );
17257
17258        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
17259        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17260            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
17261        });
17262        editor.backspace(&Default::default(), window, cx);
17263        assert_eq!(editor.text(cx), "Xa\nbbb");
17264        assert_eq!(
17265            editor.selections.ranges(&editor.display_snapshot(cx)),
17266            [Point::new(1, 0)..Point::new(1, 0)]
17267        );
17268
17269        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17270            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
17271        });
17272        editor.backspace(&Default::default(), window, cx);
17273        assert_eq!(editor.text(cx), "X\nbb");
17274        assert_eq!(
17275            editor.selections.ranges(&editor.display_snapshot(cx)),
17276            [Point::new(0, 1)..Point::new(0, 1)]
17277        );
17278    });
17279}
17280
17281#[gpui::test]
17282fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
17283    init_test(cx, |_| {});
17284
17285    let markers = vec![('[', ']').into(), ('(', ')').into()];
17286    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
17287        indoc! {"
17288            [aaaa
17289            (bbbb]
17290            cccc)",
17291        },
17292        markers.clone(),
17293    );
17294    let excerpt_ranges = markers.into_iter().map(|marker| {
17295        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
17296        ExcerptRange::new(context)
17297    });
17298    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
17299    let multibuffer = cx.new(|cx| {
17300        let mut multibuffer = MultiBuffer::new(ReadWrite);
17301        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
17302        multibuffer
17303    });
17304
17305    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
17306    editor.update_in(cx, |editor, window, cx| {
17307        let (expected_text, selection_ranges) = marked_text_ranges(
17308            indoc! {"
17309                aaaa
17310                bˇbbb
17311                bˇbbˇb
17312                cccc"
17313            },
17314            true,
17315        );
17316        assert_eq!(editor.text(cx), expected_text);
17317        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17318            s.select_ranges(
17319                selection_ranges
17320                    .iter()
17321                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end)),
17322            )
17323        });
17324
17325        editor.handle_input("X", window, cx);
17326
17327        let (expected_text, expected_selections) = marked_text_ranges(
17328            indoc! {"
17329                aaaa
17330                bXˇbbXb
17331                bXˇbbXˇb
17332                cccc"
17333            },
17334            false,
17335        );
17336        assert_eq!(editor.text(cx), expected_text);
17337        assert_eq!(
17338            editor.selections.ranges(&editor.display_snapshot(cx)),
17339            expected_selections
17340                .iter()
17341                .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
17342                .collect::<Vec<_>>()
17343        );
17344
17345        editor.newline(&Newline, window, cx);
17346        let (expected_text, expected_selections) = marked_text_ranges(
17347            indoc! {"
17348                aaaa
17349                bX
17350                ˇbbX
17351                b
17352                bX
17353                ˇbbX
17354                ˇb
17355                cccc"
17356            },
17357            false,
17358        );
17359        assert_eq!(editor.text(cx), expected_text);
17360        assert_eq!(
17361            editor.selections.ranges(&editor.display_snapshot(cx)),
17362            expected_selections
17363                .iter()
17364                .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
17365                .collect::<Vec<_>>()
17366        );
17367    });
17368}
17369
17370#[gpui::test]
17371fn test_refresh_selections(cx: &mut TestAppContext) {
17372    init_test(cx, |_| {});
17373
17374    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
17375    let mut excerpt1_id = None;
17376    let multibuffer = cx.new(|cx| {
17377        let mut multibuffer = MultiBuffer::new(ReadWrite);
17378        excerpt1_id = multibuffer
17379            .push_excerpts(
17380                buffer.clone(),
17381                [
17382                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
17383                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
17384                ],
17385                cx,
17386            )
17387            .into_iter()
17388            .next();
17389        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
17390        multibuffer
17391    });
17392
17393    let editor = cx.add_window(|window, cx| {
17394        let mut editor = build_editor(multibuffer.clone(), window, cx);
17395        let snapshot = editor.snapshot(window, cx);
17396        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17397            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
17398        });
17399        editor.begin_selection(
17400            Point::new(2, 1).to_display_point(&snapshot),
17401            true,
17402            1,
17403            window,
17404            cx,
17405        );
17406        assert_eq!(
17407            editor.selections.ranges(&editor.display_snapshot(cx)),
17408            [
17409                Point::new(1, 3)..Point::new(1, 3),
17410                Point::new(2, 1)..Point::new(2, 1),
17411            ]
17412        );
17413        editor
17414    });
17415
17416    // Refreshing selections is a no-op when excerpts haven't changed.
17417    _ = editor.update(cx, |editor, window, cx| {
17418        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
17419        assert_eq!(
17420            editor.selections.ranges(&editor.display_snapshot(cx)),
17421            [
17422                Point::new(1, 3)..Point::new(1, 3),
17423                Point::new(2, 1)..Point::new(2, 1),
17424            ]
17425        );
17426    });
17427
17428    multibuffer.update(cx, |multibuffer, cx| {
17429        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
17430    });
17431    _ = editor.update(cx, |editor, window, cx| {
17432        // Removing an excerpt causes the first selection to become degenerate.
17433        assert_eq!(
17434            editor.selections.ranges(&editor.display_snapshot(cx)),
17435            [
17436                Point::new(0, 0)..Point::new(0, 0),
17437                Point::new(0, 1)..Point::new(0, 1)
17438            ]
17439        );
17440
17441        // Refreshing selections will relocate the first selection to the original buffer
17442        // location.
17443        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
17444        assert_eq!(
17445            editor.selections.ranges(&editor.display_snapshot(cx)),
17446            [
17447                Point::new(0, 1)..Point::new(0, 1),
17448                Point::new(0, 3)..Point::new(0, 3)
17449            ]
17450        );
17451        assert!(editor.selections.pending_anchor().is_some());
17452    });
17453}
17454
17455#[gpui::test]
17456fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
17457    init_test(cx, |_| {});
17458
17459    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
17460    let mut excerpt1_id = None;
17461    let multibuffer = cx.new(|cx| {
17462        let mut multibuffer = MultiBuffer::new(ReadWrite);
17463        excerpt1_id = multibuffer
17464            .push_excerpts(
17465                buffer.clone(),
17466                [
17467                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
17468                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
17469                ],
17470                cx,
17471            )
17472            .into_iter()
17473            .next();
17474        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
17475        multibuffer
17476    });
17477
17478    let editor = cx.add_window(|window, cx| {
17479        let mut editor = build_editor(multibuffer.clone(), window, cx);
17480        let snapshot = editor.snapshot(window, cx);
17481        editor.begin_selection(
17482            Point::new(1, 3).to_display_point(&snapshot),
17483            false,
17484            1,
17485            window,
17486            cx,
17487        );
17488        assert_eq!(
17489            editor.selections.ranges(&editor.display_snapshot(cx)),
17490            [Point::new(1, 3)..Point::new(1, 3)]
17491        );
17492        editor
17493    });
17494
17495    multibuffer.update(cx, |multibuffer, cx| {
17496        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
17497    });
17498    _ = editor.update(cx, |editor, window, cx| {
17499        assert_eq!(
17500            editor.selections.ranges(&editor.display_snapshot(cx)),
17501            [Point::new(0, 0)..Point::new(0, 0)]
17502        );
17503
17504        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
17505        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
17506        assert_eq!(
17507            editor.selections.ranges(&editor.display_snapshot(cx)),
17508            [Point::new(0, 3)..Point::new(0, 3)]
17509        );
17510        assert!(editor.selections.pending_anchor().is_some());
17511    });
17512}
17513
17514#[gpui::test]
17515async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
17516    init_test(cx, |_| {});
17517
17518    let language = Arc::new(
17519        Language::new(
17520            LanguageConfig {
17521                brackets: BracketPairConfig {
17522                    pairs: vec![
17523                        BracketPair {
17524                            start: "{".to_string(),
17525                            end: "}".to_string(),
17526                            close: true,
17527                            surround: true,
17528                            newline: true,
17529                        },
17530                        BracketPair {
17531                            start: "/* ".to_string(),
17532                            end: " */".to_string(),
17533                            close: true,
17534                            surround: true,
17535                            newline: true,
17536                        },
17537                    ],
17538                    ..Default::default()
17539                },
17540                ..Default::default()
17541            },
17542            Some(tree_sitter_rust::LANGUAGE.into()),
17543        )
17544        .with_indents_query("")
17545        .unwrap(),
17546    );
17547
17548    let text = concat!(
17549        "{   }\n",     //
17550        "  x\n",       //
17551        "  /*   */\n", //
17552        "x\n",         //
17553        "{{} }\n",     //
17554    );
17555
17556    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
17557    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
17558    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
17559    editor
17560        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
17561        .await;
17562
17563    editor.update_in(cx, |editor, window, cx| {
17564        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17565            s.select_display_ranges([
17566                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
17567                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
17568                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
17569            ])
17570        });
17571        editor.newline(&Newline, window, cx);
17572
17573        assert_eq!(
17574            editor.buffer().read(cx).read(cx).text(),
17575            concat!(
17576                "{ \n",    // Suppress rustfmt
17577                "\n",      //
17578                "}\n",     //
17579                "  x\n",   //
17580                "  /* \n", //
17581                "  \n",    //
17582                "  */\n",  //
17583                "x\n",     //
17584                "{{} \n",  //
17585                "}\n",     //
17586            )
17587        );
17588    });
17589}
17590
17591#[gpui::test]
17592fn test_highlighted_ranges(cx: &mut TestAppContext) {
17593    init_test(cx, |_| {});
17594
17595    let editor = cx.add_window(|window, cx| {
17596        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
17597        build_editor(buffer, window, cx)
17598    });
17599
17600    _ = editor.update(cx, |editor, window, cx| {
17601        struct Type1;
17602        struct Type2;
17603
17604        let buffer = editor.buffer.read(cx).snapshot(cx);
17605
17606        let anchor_range =
17607            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
17608
17609        editor.highlight_background::<Type1>(
17610            &[
17611                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
17612                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
17613                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
17614                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
17615            ],
17616            |_, _| Hsla::red(),
17617            cx,
17618        );
17619        editor.highlight_background::<Type2>(
17620            &[
17621                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
17622                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
17623                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
17624                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
17625            ],
17626            |_, _| Hsla::green(),
17627            cx,
17628        );
17629
17630        let snapshot = editor.snapshot(window, cx);
17631        let highlighted_ranges = editor.sorted_background_highlights_in_range(
17632            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
17633            &snapshot,
17634            cx.theme(),
17635        );
17636        assert_eq!(
17637            highlighted_ranges,
17638            &[
17639                (
17640                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
17641                    Hsla::green(),
17642                ),
17643                (
17644                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
17645                    Hsla::red(),
17646                ),
17647                (
17648                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
17649                    Hsla::green(),
17650                ),
17651                (
17652                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
17653                    Hsla::red(),
17654                ),
17655            ]
17656        );
17657        assert_eq!(
17658            editor.sorted_background_highlights_in_range(
17659                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
17660                &snapshot,
17661                cx.theme(),
17662            ),
17663            &[(
17664                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
17665                Hsla::red(),
17666            )]
17667        );
17668    });
17669}
17670
17671#[gpui::test]
17672async fn test_following(cx: &mut TestAppContext) {
17673    init_test(cx, |_| {});
17674
17675    let fs = FakeFs::new(cx.executor());
17676    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
17677
17678    let buffer = project.update(cx, |project, cx| {
17679        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, false, cx);
17680        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
17681    });
17682    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
17683    let follower = cx.update(|cx| {
17684        cx.open_window(
17685            WindowOptions {
17686                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
17687                    gpui::Point::new(px(0.), px(0.)),
17688                    gpui::Point::new(px(10.), px(80.)),
17689                ))),
17690                ..Default::default()
17691            },
17692            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
17693        )
17694        .unwrap()
17695    });
17696
17697    let is_still_following = Rc::new(RefCell::new(true));
17698    let follower_edit_event_count = Rc::new(RefCell::new(0));
17699    let pending_update = Rc::new(RefCell::new(None));
17700    let leader_entity = leader.root(cx).unwrap();
17701    let follower_entity = follower.root(cx).unwrap();
17702    _ = follower.update(cx, {
17703        let update = pending_update.clone();
17704        let is_still_following = is_still_following.clone();
17705        let follower_edit_event_count = follower_edit_event_count.clone();
17706        |_, window, cx| {
17707            cx.subscribe_in(
17708                &leader_entity,
17709                window,
17710                move |_, leader, event, window, cx| {
17711                    leader.read(cx).add_event_to_update_proto(
17712                        event,
17713                        &mut update.borrow_mut(),
17714                        window,
17715                        cx,
17716                    );
17717                },
17718            )
17719            .detach();
17720
17721            cx.subscribe_in(
17722                &follower_entity,
17723                window,
17724                move |_, _, event: &EditorEvent, _window, _cx| {
17725                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
17726                        *is_still_following.borrow_mut() = false;
17727                    }
17728
17729                    if let EditorEvent::BufferEdited = event {
17730                        *follower_edit_event_count.borrow_mut() += 1;
17731                    }
17732                },
17733            )
17734            .detach();
17735        }
17736    });
17737
17738    // Update the selections only
17739    _ = leader.update(cx, |leader, window, cx| {
17740        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17741            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
17742        });
17743    });
17744    follower
17745        .update(cx, |follower, window, cx| {
17746            follower.apply_update_proto(
17747                &project,
17748                pending_update.borrow_mut().take().unwrap(),
17749                window,
17750                cx,
17751            )
17752        })
17753        .unwrap()
17754        .await
17755        .unwrap();
17756    _ = follower.update(cx, |follower, _, cx| {
17757        assert_eq!(
17758            follower.selections.ranges(&follower.display_snapshot(cx)),
17759            vec![MultiBufferOffset(1)..MultiBufferOffset(1)]
17760        );
17761    });
17762    assert!(*is_still_following.borrow());
17763    assert_eq!(*follower_edit_event_count.borrow(), 0);
17764
17765    // Update the scroll position only
17766    _ = leader.update(cx, |leader, window, cx| {
17767        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
17768    });
17769    follower
17770        .update(cx, |follower, window, cx| {
17771            follower.apply_update_proto(
17772                &project,
17773                pending_update.borrow_mut().take().unwrap(),
17774                window,
17775                cx,
17776            )
17777        })
17778        .unwrap()
17779        .await
17780        .unwrap();
17781    assert_eq!(
17782        follower
17783            .update(cx, |follower, _, cx| follower.scroll_position(cx))
17784            .unwrap(),
17785        gpui::Point::new(1.5, 3.5)
17786    );
17787    assert!(*is_still_following.borrow());
17788    assert_eq!(*follower_edit_event_count.borrow(), 0);
17789
17790    // Update the selections and scroll position. The follower's scroll position is updated
17791    // via autoscroll, not via the leader's exact scroll position.
17792    _ = leader.update(cx, |leader, window, cx| {
17793        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17794            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
17795        });
17796        leader.request_autoscroll(Autoscroll::newest(), cx);
17797        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
17798    });
17799    follower
17800        .update(cx, |follower, window, cx| {
17801            follower.apply_update_proto(
17802                &project,
17803                pending_update.borrow_mut().take().unwrap(),
17804                window,
17805                cx,
17806            )
17807        })
17808        .unwrap()
17809        .await
17810        .unwrap();
17811    _ = follower.update(cx, |follower, _, cx| {
17812        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
17813        assert_eq!(
17814            follower.selections.ranges(&follower.display_snapshot(cx)),
17815            vec![MultiBufferOffset(0)..MultiBufferOffset(0)]
17816        );
17817    });
17818    assert!(*is_still_following.borrow());
17819
17820    // Creating a pending selection that precedes another selection
17821    _ = leader.update(cx, |leader, window, cx| {
17822        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17823            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
17824        });
17825        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
17826    });
17827    follower
17828        .update(cx, |follower, window, cx| {
17829            follower.apply_update_proto(
17830                &project,
17831                pending_update.borrow_mut().take().unwrap(),
17832                window,
17833                cx,
17834            )
17835        })
17836        .unwrap()
17837        .await
17838        .unwrap();
17839    _ = follower.update(cx, |follower, _, cx| {
17840        assert_eq!(
17841            follower.selections.ranges(&follower.display_snapshot(cx)),
17842            vec![
17843                MultiBufferOffset(0)..MultiBufferOffset(0),
17844                MultiBufferOffset(1)..MultiBufferOffset(1)
17845            ]
17846        );
17847    });
17848    assert!(*is_still_following.borrow());
17849
17850    // Extend the pending selection so that it surrounds another selection
17851    _ = leader.update(cx, |leader, window, cx| {
17852        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
17853    });
17854    follower
17855        .update(cx, |follower, window, cx| {
17856            follower.apply_update_proto(
17857                &project,
17858                pending_update.borrow_mut().take().unwrap(),
17859                window,
17860                cx,
17861            )
17862        })
17863        .unwrap()
17864        .await
17865        .unwrap();
17866    _ = follower.update(cx, |follower, _, cx| {
17867        assert_eq!(
17868            follower.selections.ranges(&follower.display_snapshot(cx)),
17869            vec![MultiBufferOffset(0)..MultiBufferOffset(2)]
17870        );
17871    });
17872
17873    // Scrolling locally breaks the follow
17874    _ = follower.update(cx, |follower, window, cx| {
17875        let top_anchor = follower
17876            .buffer()
17877            .read(cx)
17878            .read(cx)
17879            .anchor_after(MultiBufferOffset(0));
17880        follower.set_scroll_anchor(
17881            ScrollAnchor {
17882                anchor: top_anchor,
17883                offset: gpui::Point::new(0.0, 0.5),
17884            },
17885            window,
17886            cx,
17887        );
17888    });
17889    assert!(!(*is_still_following.borrow()));
17890}
17891
17892#[gpui::test]
17893async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
17894    init_test(cx, |_| {});
17895
17896    let fs = FakeFs::new(cx.executor());
17897    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
17898    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17899    let pane = workspace
17900        .update(cx, |workspace, _, _| workspace.active_pane().clone())
17901        .unwrap();
17902
17903    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17904
17905    let leader = pane.update_in(cx, |_, window, cx| {
17906        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
17907        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
17908    });
17909
17910    // Start following the editor when it has no excerpts.
17911    let mut state_message =
17912        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
17913    let workspace_entity = workspace.root(cx).unwrap();
17914    let follower_1 = cx
17915        .update_window(*workspace.deref(), |_, window, cx| {
17916            Editor::from_state_proto(
17917                workspace_entity,
17918                ViewId {
17919                    creator: CollaboratorId::PeerId(PeerId::default()),
17920                    id: 0,
17921                },
17922                &mut state_message,
17923                window,
17924                cx,
17925            )
17926        })
17927        .unwrap()
17928        .unwrap()
17929        .await
17930        .unwrap();
17931
17932    let update_message = Rc::new(RefCell::new(None));
17933    follower_1.update_in(cx, {
17934        let update = update_message.clone();
17935        |_, window, cx| {
17936            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
17937                leader.read(cx).add_event_to_update_proto(
17938                    event,
17939                    &mut update.borrow_mut(),
17940                    window,
17941                    cx,
17942                );
17943            })
17944            .detach();
17945        }
17946    });
17947
17948    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
17949        (
17950            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, false, cx),
17951            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, false, cx),
17952        )
17953    });
17954
17955    // Insert some excerpts.
17956    leader.update(cx, |leader, cx| {
17957        leader.buffer.update(cx, |multibuffer, cx| {
17958            multibuffer.set_excerpts_for_path(
17959                PathKey::with_sort_prefix(1, rel_path("b.txt").into_arc()),
17960                buffer_1.clone(),
17961                vec![
17962                    Point::row_range(0..3),
17963                    Point::row_range(1..6),
17964                    Point::row_range(12..15),
17965                ],
17966                0,
17967                cx,
17968            );
17969            multibuffer.set_excerpts_for_path(
17970                PathKey::with_sort_prefix(1, rel_path("a.txt").into_arc()),
17971                buffer_2.clone(),
17972                vec![Point::row_range(0..6), Point::row_range(8..12)],
17973                0,
17974                cx,
17975            );
17976        });
17977    });
17978
17979    // Apply the update of adding the excerpts.
17980    follower_1
17981        .update_in(cx, |follower, window, cx| {
17982            follower.apply_update_proto(
17983                &project,
17984                update_message.borrow().clone().unwrap(),
17985                window,
17986                cx,
17987            )
17988        })
17989        .await
17990        .unwrap();
17991    assert_eq!(
17992        follower_1.update(cx, |editor, cx| editor.text(cx)),
17993        leader.update(cx, |editor, cx| editor.text(cx))
17994    );
17995    update_message.borrow_mut().take();
17996
17997    // Start following separately after it already has excerpts.
17998    let mut state_message =
17999        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
18000    let workspace_entity = workspace.root(cx).unwrap();
18001    let follower_2 = cx
18002        .update_window(*workspace.deref(), |_, window, cx| {
18003            Editor::from_state_proto(
18004                workspace_entity,
18005                ViewId {
18006                    creator: CollaboratorId::PeerId(PeerId::default()),
18007                    id: 0,
18008                },
18009                &mut state_message,
18010                window,
18011                cx,
18012            )
18013        })
18014        .unwrap()
18015        .unwrap()
18016        .await
18017        .unwrap();
18018    assert_eq!(
18019        follower_2.update(cx, |editor, cx| editor.text(cx)),
18020        leader.update(cx, |editor, cx| editor.text(cx))
18021    );
18022
18023    // Remove some excerpts.
18024    leader.update(cx, |leader, cx| {
18025        leader.buffer.update(cx, |multibuffer, cx| {
18026            let excerpt_ids = multibuffer.excerpt_ids();
18027            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
18028            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
18029        });
18030    });
18031
18032    // Apply the update of removing the excerpts.
18033    follower_1
18034        .update_in(cx, |follower, window, cx| {
18035            follower.apply_update_proto(
18036                &project,
18037                update_message.borrow().clone().unwrap(),
18038                window,
18039                cx,
18040            )
18041        })
18042        .await
18043        .unwrap();
18044    follower_2
18045        .update_in(cx, |follower, window, cx| {
18046            follower.apply_update_proto(
18047                &project,
18048                update_message.borrow().clone().unwrap(),
18049                window,
18050                cx,
18051            )
18052        })
18053        .await
18054        .unwrap();
18055    update_message.borrow_mut().take();
18056    assert_eq!(
18057        follower_1.update(cx, |editor, cx| editor.text(cx)),
18058        leader.update(cx, |editor, cx| editor.text(cx))
18059    );
18060}
18061
18062#[gpui::test]
18063async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
18064    init_test(cx, |_| {});
18065
18066    let mut cx = EditorTestContext::new(cx).await;
18067    let lsp_store =
18068        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
18069
18070    cx.set_state(indoc! {"
18071        ˇfn func(abc def: i32) -> u32 {
18072        }
18073    "});
18074
18075    cx.update(|_, cx| {
18076        lsp_store.update(cx, |lsp_store, cx| {
18077            lsp_store
18078                .update_diagnostics(
18079                    LanguageServerId(0),
18080                    lsp::PublishDiagnosticsParams {
18081                        uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
18082                        version: None,
18083                        diagnostics: vec![
18084                            lsp::Diagnostic {
18085                                range: lsp::Range::new(
18086                                    lsp::Position::new(0, 11),
18087                                    lsp::Position::new(0, 12),
18088                                ),
18089                                severity: Some(lsp::DiagnosticSeverity::ERROR),
18090                                ..Default::default()
18091                            },
18092                            lsp::Diagnostic {
18093                                range: lsp::Range::new(
18094                                    lsp::Position::new(0, 12),
18095                                    lsp::Position::new(0, 15),
18096                                ),
18097                                severity: Some(lsp::DiagnosticSeverity::ERROR),
18098                                ..Default::default()
18099                            },
18100                            lsp::Diagnostic {
18101                                range: lsp::Range::new(
18102                                    lsp::Position::new(0, 25),
18103                                    lsp::Position::new(0, 28),
18104                                ),
18105                                severity: Some(lsp::DiagnosticSeverity::ERROR),
18106                                ..Default::default()
18107                            },
18108                        ],
18109                    },
18110                    None,
18111                    DiagnosticSourceKind::Pushed,
18112                    &[],
18113                    cx,
18114                )
18115                .unwrap()
18116        });
18117    });
18118
18119    executor.run_until_parked();
18120
18121    cx.update_editor(|editor, window, cx| {
18122        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
18123    });
18124
18125    cx.assert_editor_state(indoc! {"
18126        fn func(abc def: i32) -> ˇu32 {
18127        }
18128    "});
18129
18130    cx.update_editor(|editor, window, cx| {
18131        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
18132    });
18133
18134    cx.assert_editor_state(indoc! {"
18135        fn func(abc ˇdef: i32) -> u32 {
18136        }
18137    "});
18138
18139    cx.update_editor(|editor, window, cx| {
18140        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
18141    });
18142
18143    cx.assert_editor_state(indoc! {"
18144        fn func(abcˇ def: i32) -> u32 {
18145        }
18146    "});
18147
18148    cx.update_editor(|editor, window, cx| {
18149        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
18150    });
18151
18152    cx.assert_editor_state(indoc! {"
18153        fn func(abc def: i32) -> ˇu32 {
18154        }
18155    "});
18156}
18157
18158#[gpui::test]
18159async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
18160    init_test(cx, |_| {});
18161
18162    let mut cx = EditorTestContext::new(cx).await;
18163
18164    let diff_base = r#"
18165        use some::mod;
18166
18167        const A: u32 = 42;
18168
18169        fn main() {
18170            println!("hello");
18171
18172            println!("world");
18173        }
18174        "#
18175    .unindent();
18176
18177    // Edits are modified, removed, modified, added
18178    cx.set_state(
18179        &r#"
18180        use some::modified;
18181
18182        ˇ
18183        fn main() {
18184            println!("hello there");
18185
18186            println!("around the");
18187            println!("world");
18188        }
18189        "#
18190        .unindent(),
18191    );
18192
18193    cx.set_head_text(&diff_base);
18194    executor.run_until_parked();
18195
18196    cx.update_editor(|editor, window, cx| {
18197        //Wrap around the bottom of the buffer
18198        for _ in 0..3 {
18199            editor.go_to_next_hunk(&GoToHunk, window, cx);
18200        }
18201    });
18202
18203    cx.assert_editor_state(
18204        &r#"
18205        ˇuse some::modified;
18206
18207
18208        fn main() {
18209            println!("hello there");
18210
18211            println!("around the");
18212            println!("world");
18213        }
18214        "#
18215        .unindent(),
18216    );
18217
18218    cx.update_editor(|editor, window, cx| {
18219        //Wrap around the top of the buffer
18220        for _ in 0..2 {
18221            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
18222        }
18223    });
18224
18225    cx.assert_editor_state(
18226        &r#"
18227        use some::modified;
18228
18229
18230        fn main() {
18231        ˇ    println!("hello there");
18232
18233            println!("around the");
18234            println!("world");
18235        }
18236        "#
18237        .unindent(),
18238    );
18239
18240    cx.update_editor(|editor, window, cx| {
18241        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
18242    });
18243
18244    cx.assert_editor_state(
18245        &r#"
18246        use some::modified;
18247
18248        ˇ
18249        fn main() {
18250            println!("hello there");
18251
18252            println!("around the");
18253            println!("world");
18254        }
18255        "#
18256        .unindent(),
18257    );
18258
18259    cx.update_editor(|editor, window, cx| {
18260        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
18261    });
18262
18263    cx.assert_editor_state(
18264        &r#"
18265        ˇuse some::modified;
18266
18267
18268        fn main() {
18269            println!("hello there");
18270
18271            println!("around the");
18272            println!("world");
18273        }
18274        "#
18275        .unindent(),
18276    );
18277
18278    cx.update_editor(|editor, window, cx| {
18279        for _ in 0..2 {
18280            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
18281        }
18282    });
18283
18284    cx.assert_editor_state(
18285        &r#"
18286        use some::modified;
18287
18288
18289        fn main() {
18290        ˇ    println!("hello there");
18291
18292            println!("around the");
18293            println!("world");
18294        }
18295        "#
18296        .unindent(),
18297    );
18298
18299    cx.update_editor(|editor, window, cx| {
18300        editor.fold(&Fold, window, cx);
18301    });
18302
18303    cx.update_editor(|editor, window, cx| {
18304        editor.go_to_next_hunk(&GoToHunk, window, cx);
18305    });
18306
18307    cx.assert_editor_state(
18308        &r#"
18309        ˇuse some::modified;
18310
18311
18312        fn main() {
18313            println!("hello there");
18314
18315            println!("around the");
18316            println!("world");
18317        }
18318        "#
18319        .unindent(),
18320    );
18321}
18322
18323#[test]
18324fn test_split_words() {
18325    fn split(text: &str) -> Vec<&str> {
18326        split_words(text).collect()
18327    }
18328
18329    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
18330    assert_eq!(split("hello_world"), &["hello_", "world"]);
18331    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
18332    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
18333    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
18334    assert_eq!(split("helloworld"), &["helloworld"]);
18335
18336    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
18337}
18338
18339#[test]
18340fn test_split_words_for_snippet_prefix() {
18341    fn split(text: &str) -> Vec<&str> {
18342        snippet_candidate_suffixes(text, |c| c.is_alphanumeric() || c == '_').collect()
18343    }
18344
18345    assert_eq!(split("HelloWorld"), &["HelloWorld"]);
18346    assert_eq!(split("hello_world"), &["hello_world"]);
18347    assert_eq!(split("_hello_world_"), &["_hello_world_"]);
18348    assert_eq!(split("Hello_World"), &["Hello_World"]);
18349    assert_eq!(split("helloWOrld"), &["helloWOrld"]);
18350    assert_eq!(split("helloworld"), &["helloworld"]);
18351    assert_eq!(
18352        split("this@is!@#$^many   . symbols"),
18353        &[
18354            "symbols",
18355            " symbols",
18356            ". symbols",
18357            " . symbols",
18358            "  . symbols",
18359            "   . symbols",
18360            "many   . symbols",
18361            "^many   . symbols",
18362            "$^many   . symbols",
18363            "#$^many   . symbols",
18364            "@#$^many   . symbols",
18365            "!@#$^many   . symbols",
18366            "is!@#$^many   . symbols",
18367            "@is!@#$^many   . symbols",
18368            "this@is!@#$^many   . symbols",
18369        ],
18370    );
18371    assert_eq!(split("a.s"), &["s", ".s", "a.s"]);
18372}
18373
18374#[gpui::test]
18375async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
18376    init_test(cx, |_| {});
18377
18378    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
18379
18380    #[track_caller]
18381    fn assert(before: &str, after: &str, cx: &mut EditorLspTestContext) {
18382        let _state_context = cx.set_state(before);
18383        cx.run_until_parked();
18384        cx.update_editor(|editor, window, cx| {
18385            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
18386        });
18387        cx.run_until_parked();
18388        cx.assert_editor_state(after);
18389    }
18390
18391    // Outside bracket jumps to outside of matching bracket
18392    assert("console.logˇ(var);", "console.log(var)ˇ;", &mut cx);
18393    assert("console.log(var)ˇ;", "console.logˇ(var);", &mut cx);
18394
18395    // Inside bracket jumps to inside of matching bracket
18396    assert("console.log(ˇvar);", "console.log(varˇ);", &mut cx);
18397    assert("console.log(varˇ);", "console.log(ˇvar);", &mut cx);
18398
18399    // When outside a bracket and inside, favor jumping to the inside bracket
18400    assert(
18401        "console.log('foo', [1, 2, 3]ˇ);",
18402        "console.log('foo', ˇ[1, 2, 3]);",
18403        &mut cx,
18404    );
18405    assert(
18406        "console.log(ˇ'foo', [1, 2, 3]);",
18407        "console.log('foo'ˇ, [1, 2, 3]);",
18408        &mut cx,
18409    );
18410
18411    // Bias forward if two options are equally likely
18412    assert(
18413        "let result = curried_fun()ˇ();",
18414        "let result = curried_fun()()ˇ;",
18415        &mut cx,
18416    );
18417
18418    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
18419    assert(
18420        indoc! {"
18421            function test() {
18422                console.log('test')ˇ
18423            }"},
18424        indoc! {"
18425            function test() {
18426                console.logˇ('test')
18427            }"},
18428        &mut cx,
18429    );
18430}
18431
18432#[gpui::test]
18433async fn test_move_to_enclosing_bracket_in_markdown_code_block(cx: &mut TestAppContext) {
18434    init_test(cx, |_| {});
18435    let language_registry = Arc::new(language::LanguageRegistry::test(cx.executor()));
18436    language_registry.add(markdown_lang());
18437    language_registry.add(rust_lang());
18438    let buffer = cx.new(|cx| {
18439        let mut buffer = language::Buffer::local(
18440            indoc! {"
18441            ```rs
18442            impl Worktree {
18443                pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
18444                }
18445            }
18446            ```
18447        "},
18448            cx,
18449        );
18450        buffer.set_language_registry(language_registry.clone());
18451        buffer.set_language(Some(markdown_lang()), cx);
18452        buffer
18453    });
18454    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
18455    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
18456    cx.executor().run_until_parked();
18457    _ = editor.update(cx, |editor, window, cx| {
18458        // Case 1: Test outer enclosing brackets
18459        select_ranges(
18460            editor,
18461            &indoc! {"
18462                ```rs
18463                impl Worktree {
18464                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
18465                    }
1846618467                ```
18468            "},
18469            window,
18470            cx,
18471        );
18472        editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx);
18473        assert_text_with_selections(
18474            editor,
18475            &indoc! {"
18476                ```rs
18477                impl Worktree ˇ{
18478                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
18479                    }
18480                }
18481                ```
18482            "},
18483            cx,
18484        );
18485        // Case 2: Test inner enclosing brackets
18486        select_ranges(
18487            editor,
18488            &indoc! {"
18489                ```rs
18490                impl Worktree {
18491                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
1849218493                }
18494                ```
18495            "},
18496            window,
18497            cx,
18498        );
18499        editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx);
18500        assert_text_with_selections(
18501            editor,
18502            &indoc! {"
18503                ```rs
18504                impl Worktree {
18505                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> ˇ{
18506                    }
18507                }
18508                ```
18509            "},
18510            cx,
18511        );
18512    });
18513}
18514
18515#[gpui::test]
18516async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
18517    init_test(cx, |_| {});
18518
18519    let fs = FakeFs::new(cx.executor());
18520    fs.insert_tree(
18521        path!("/a"),
18522        json!({
18523            "main.rs": "fn main() { let a = 5; }",
18524            "other.rs": "// Test file",
18525        }),
18526    )
18527    .await;
18528    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18529
18530    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18531    language_registry.add(Arc::new(Language::new(
18532        LanguageConfig {
18533            name: "Rust".into(),
18534            matcher: LanguageMatcher {
18535                path_suffixes: vec!["rs".to_string()],
18536                ..Default::default()
18537            },
18538            brackets: BracketPairConfig {
18539                pairs: vec![BracketPair {
18540                    start: "{".to_string(),
18541                    end: "}".to_string(),
18542                    close: true,
18543                    surround: true,
18544                    newline: true,
18545                }],
18546                disabled_scopes_by_bracket_ix: Vec::new(),
18547            },
18548            ..Default::default()
18549        },
18550        Some(tree_sitter_rust::LANGUAGE.into()),
18551    )));
18552    let mut fake_servers = language_registry.register_fake_lsp(
18553        "Rust",
18554        FakeLspAdapter {
18555            capabilities: lsp::ServerCapabilities {
18556                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
18557                    first_trigger_character: "{".to_string(),
18558                    more_trigger_character: None,
18559                }),
18560                ..Default::default()
18561            },
18562            ..Default::default()
18563        },
18564    );
18565
18566    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18567
18568    let cx = &mut VisualTestContext::from_window(*workspace, cx);
18569
18570    let worktree_id = workspace
18571        .update(cx, |workspace, _, cx| {
18572            workspace.project().update(cx, |project, cx| {
18573                project.worktrees(cx).next().unwrap().read(cx).id()
18574            })
18575        })
18576        .unwrap();
18577
18578    let buffer = project
18579        .update(cx, |project, cx| {
18580            project.open_local_buffer(path!("/a/main.rs"), cx)
18581        })
18582        .await
18583        .unwrap();
18584    let editor_handle = workspace
18585        .update(cx, |workspace, window, cx| {
18586            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
18587        })
18588        .unwrap()
18589        .await
18590        .unwrap()
18591        .downcast::<Editor>()
18592        .unwrap();
18593
18594    let fake_server = fake_servers.next().await.unwrap();
18595
18596    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
18597        |params, _| async move {
18598            assert_eq!(
18599                params.text_document_position.text_document.uri,
18600                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
18601            );
18602            assert_eq!(
18603                params.text_document_position.position,
18604                lsp::Position::new(0, 21),
18605            );
18606
18607            Ok(Some(vec![lsp::TextEdit {
18608                new_text: "]".to_string(),
18609                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18610            }]))
18611        },
18612    );
18613
18614    editor_handle.update_in(cx, |editor, window, cx| {
18615        window.focus(&editor.focus_handle(cx), cx);
18616        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18617            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
18618        });
18619        editor.handle_input("{", window, cx);
18620    });
18621
18622    cx.executor().run_until_parked();
18623
18624    buffer.update(cx, |buffer, _| {
18625        assert_eq!(
18626            buffer.text(),
18627            "fn main() { let a = {5}; }",
18628            "No extra braces from on type formatting should appear in the buffer"
18629        )
18630    });
18631}
18632
18633#[gpui::test(iterations = 20, seeds(31))]
18634async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
18635    init_test(cx, |_| {});
18636
18637    let mut cx = EditorLspTestContext::new_rust(
18638        lsp::ServerCapabilities {
18639            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
18640                first_trigger_character: ".".to_string(),
18641                more_trigger_character: None,
18642            }),
18643            ..Default::default()
18644        },
18645        cx,
18646    )
18647    .await;
18648
18649    cx.update_buffer(|buffer, _| {
18650        // This causes autoindent to be async.
18651        buffer.set_sync_parse_timeout(None)
18652    });
18653
18654    cx.set_state("fn c() {\n    d()ˇ\n}\n");
18655    cx.simulate_keystroke("\n");
18656    cx.run_until_parked();
18657
18658    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
18659    let mut request =
18660        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
18661            let buffer_cloned = buffer_cloned.clone();
18662            async move {
18663                buffer_cloned.update(&mut cx, |buffer, _| {
18664                    assert_eq!(
18665                        buffer.text(),
18666                        "fn c() {\n    d()\n        .\n}\n",
18667                        "OnTypeFormatting should triggered after autoindent applied"
18668                    )
18669                });
18670
18671                Ok(Some(vec![]))
18672            }
18673        });
18674
18675    cx.simulate_keystroke(".");
18676    cx.run_until_parked();
18677
18678    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
18679    assert!(request.next().await.is_some());
18680    request.close();
18681    assert!(request.next().await.is_none());
18682}
18683
18684#[gpui::test]
18685async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
18686    init_test(cx, |_| {});
18687
18688    let fs = FakeFs::new(cx.executor());
18689    fs.insert_tree(
18690        path!("/a"),
18691        json!({
18692            "main.rs": "fn main() { let a = 5; }",
18693            "other.rs": "// Test file",
18694        }),
18695    )
18696    .await;
18697
18698    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18699
18700    let server_restarts = Arc::new(AtomicUsize::new(0));
18701    let closure_restarts = Arc::clone(&server_restarts);
18702    let language_server_name = "test language server";
18703    let language_name: LanguageName = "Rust".into();
18704
18705    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18706    language_registry.add(Arc::new(Language::new(
18707        LanguageConfig {
18708            name: language_name.clone(),
18709            matcher: LanguageMatcher {
18710                path_suffixes: vec!["rs".to_string()],
18711                ..Default::default()
18712            },
18713            ..Default::default()
18714        },
18715        Some(tree_sitter_rust::LANGUAGE.into()),
18716    )));
18717    let mut fake_servers = language_registry.register_fake_lsp(
18718        "Rust",
18719        FakeLspAdapter {
18720            name: language_server_name,
18721            initialization_options: Some(json!({
18722                "testOptionValue": true
18723            })),
18724            initializer: Some(Box::new(move |fake_server| {
18725                let task_restarts = Arc::clone(&closure_restarts);
18726                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
18727                    task_restarts.fetch_add(1, atomic::Ordering::Release);
18728                    futures::future::ready(Ok(()))
18729                });
18730            })),
18731            ..Default::default()
18732        },
18733    );
18734
18735    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18736    let _buffer = project
18737        .update(cx, |project, cx| {
18738            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
18739        })
18740        .await
18741        .unwrap();
18742    let _fake_server = fake_servers.next().await.unwrap();
18743    update_test_language_settings(cx, |language_settings| {
18744        language_settings.languages.0.insert(
18745            language_name.clone().0.to_string(),
18746            LanguageSettingsContent {
18747                tab_size: NonZeroU32::new(8),
18748                ..Default::default()
18749            },
18750        );
18751    });
18752    cx.executor().run_until_parked();
18753    assert_eq!(
18754        server_restarts.load(atomic::Ordering::Acquire),
18755        0,
18756        "Should not restart LSP server on an unrelated change"
18757    );
18758
18759    update_test_project_settings(cx, |project_settings| {
18760        project_settings.lsp.0.insert(
18761            "Some other server name".into(),
18762            LspSettings {
18763                binary: None,
18764                settings: None,
18765                initialization_options: Some(json!({
18766                    "some other init value": false
18767                })),
18768                enable_lsp_tasks: false,
18769                fetch: None,
18770            },
18771        );
18772    });
18773    cx.executor().run_until_parked();
18774    assert_eq!(
18775        server_restarts.load(atomic::Ordering::Acquire),
18776        0,
18777        "Should not restart LSP server on an unrelated LSP settings change"
18778    );
18779
18780    update_test_project_settings(cx, |project_settings| {
18781        project_settings.lsp.0.insert(
18782            language_server_name.into(),
18783            LspSettings {
18784                binary: None,
18785                settings: None,
18786                initialization_options: Some(json!({
18787                    "anotherInitValue": false
18788                })),
18789                enable_lsp_tasks: false,
18790                fetch: None,
18791            },
18792        );
18793    });
18794    cx.executor().run_until_parked();
18795    assert_eq!(
18796        server_restarts.load(atomic::Ordering::Acquire),
18797        1,
18798        "Should restart LSP server on a related LSP settings change"
18799    );
18800
18801    update_test_project_settings(cx, |project_settings| {
18802        project_settings.lsp.0.insert(
18803            language_server_name.into(),
18804            LspSettings {
18805                binary: None,
18806                settings: None,
18807                initialization_options: Some(json!({
18808                    "anotherInitValue": false
18809                })),
18810                enable_lsp_tasks: false,
18811                fetch: None,
18812            },
18813        );
18814    });
18815    cx.executor().run_until_parked();
18816    assert_eq!(
18817        server_restarts.load(atomic::Ordering::Acquire),
18818        1,
18819        "Should not restart LSP server on a related LSP settings change that is the same"
18820    );
18821
18822    update_test_project_settings(cx, |project_settings| {
18823        project_settings.lsp.0.insert(
18824            language_server_name.into(),
18825            LspSettings {
18826                binary: None,
18827                settings: None,
18828                initialization_options: None,
18829                enable_lsp_tasks: false,
18830                fetch: None,
18831            },
18832        );
18833    });
18834    cx.executor().run_until_parked();
18835    assert_eq!(
18836        server_restarts.load(atomic::Ordering::Acquire),
18837        2,
18838        "Should restart LSP server on another related LSP settings change"
18839    );
18840}
18841
18842#[gpui::test]
18843async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
18844    init_test(cx, |_| {});
18845
18846    let mut cx = EditorLspTestContext::new_rust(
18847        lsp::ServerCapabilities {
18848            completion_provider: Some(lsp::CompletionOptions {
18849                trigger_characters: Some(vec![".".to_string()]),
18850                resolve_provider: Some(true),
18851                ..Default::default()
18852            }),
18853            ..Default::default()
18854        },
18855        cx,
18856    )
18857    .await;
18858
18859    cx.set_state("fn main() { let a = 2ˇ; }");
18860    cx.simulate_keystroke(".");
18861    let completion_item = lsp::CompletionItem {
18862        label: "some".into(),
18863        kind: Some(lsp::CompletionItemKind::SNIPPET),
18864        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
18865        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
18866            kind: lsp::MarkupKind::Markdown,
18867            value: "```rust\nSome(2)\n```".to_string(),
18868        })),
18869        deprecated: Some(false),
18870        sort_text: Some("fffffff2".to_string()),
18871        filter_text: Some("some".to_string()),
18872        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
18873        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18874            range: lsp::Range {
18875                start: lsp::Position {
18876                    line: 0,
18877                    character: 22,
18878                },
18879                end: lsp::Position {
18880                    line: 0,
18881                    character: 22,
18882                },
18883            },
18884            new_text: "Some(2)".to_string(),
18885        })),
18886        additional_text_edits: Some(vec![lsp::TextEdit {
18887            range: lsp::Range {
18888                start: lsp::Position {
18889                    line: 0,
18890                    character: 20,
18891                },
18892                end: lsp::Position {
18893                    line: 0,
18894                    character: 22,
18895                },
18896            },
18897            new_text: "".to_string(),
18898        }]),
18899        ..Default::default()
18900    };
18901
18902    let closure_completion_item = completion_item.clone();
18903    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18904        let task_completion_item = closure_completion_item.clone();
18905        async move {
18906            Ok(Some(lsp::CompletionResponse::Array(vec![
18907                task_completion_item,
18908            ])))
18909        }
18910    });
18911
18912    request.next().await;
18913
18914    cx.condition(|editor, _| editor.context_menu_visible())
18915        .await;
18916    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
18917        editor
18918            .confirm_completion(&ConfirmCompletion::default(), window, cx)
18919            .unwrap()
18920    });
18921    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
18922
18923    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
18924        let task_completion_item = completion_item.clone();
18925        async move { Ok(task_completion_item) }
18926    })
18927    .next()
18928    .await
18929    .unwrap();
18930    apply_additional_edits.await.unwrap();
18931    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
18932}
18933
18934#[gpui::test]
18935async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
18936    init_test(cx, |_| {});
18937
18938    let mut cx = EditorLspTestContext::new_rust(
18939        lsp::ServerCapabilities {
18940            completion_provider: Some(lsp::CompletionOptions {
18941                trigger_characters: Some(vec![".".to_string()]),
18942                resolve_provider: Some(true),
18943                ..Default::default()
18944            }),
18945            ..Default::default()
18946        },
18947        cx,
18948    )
18949    .await;
18950
18951    cx.set_state("fn main() { let a = 2ˇ; }");
18952    cx.simulate_keystroke(".");
18953
18954    let item1 = lsp::CompletionItem {
18955        label: "method id()".to_string(),
18956        filter_text: Some("id".to_string()),
18957        detail: None,
18958        documentation: None,
18959        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18960            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18961            new_text: ".id".to_string(),
18962        })),
18963        ..lsp::CompletionItem::default()
18964    };
18965
18966    let item2 = lsp::CompletionItem {
18967        label: "other".to_string(),
18968        filter_text: Some("other".to_string()),
18969        detail: None,
18970        documentation: None,
18971        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18972            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18973            new_text: ".other".to_string(),
18974        })),
18975        ..lsp::CompletionItem::default()
18976    };
18977
18978    let item1 = item1.clone();
18979    cx.set_request_handler::<lsp::request::Completion, _, _>({
18980        let item1 = item1.clone();
18981        move |_, _, _| {
18982            let item1 = item1.clone();
18983            let item2 = item2.clone();
18984            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
18985        }
18986    })
18987    .next()
18988    .await;
18989
18990    cx.condition(|editor, _| editor.context_menu_visible())
18991        .await;
18992    cx.update_editor(|editor, _, _| {
18993        let context_menu = editor.context_menu.borrow_mut();
18994        let context_menu = context_menu
18995            .as_ref()
18996            .expect("Should have the context menu deployed");
18997        match context_menu {
18998            CodeContextMenu::Completions(completions_menu) => {
18999                let completions = completions_menu.completions.borrow_mut();
19000                assert_eq!(
19001                    completions
19002                        .iter()
19003                        .map(|completion| &completion.label.text)
19004                        .collect::<Vec<_>>(),
19005                    vec!["method id()", "other"]
19006                )
19007            }
19008            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
19009        }
19010    });
19011
19012    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
19013        let item1 = item1.clone();
19014        move |_, item_to_resolve, _| {
19015            let item1 = item1.clone();
19016            async move {
19017                if item1 == item_to_resolve {
19018                    Ok(lsp::CompletionItem {
19019                        label: "method id()".to_string(),
19020                        filter_text: Some("id".to_string()),
19021                        detail: Some("Now resolved!".to_string()),
19022                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
19023                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
19024                            range: lsp::Range::new(
19025                                lsp::Position::new(0, 22),
19026                                lsp::Position::new(0, 22),
19027                            ),
19028                            new_text: ".id".to_string(),
19029                        })),
19030                        ..lsp::CompletionItem::default()
19031                    })
19032                } else {
19033                    Ok(item_to_resolve)
19034                }
19035            }
19036        }
19037    })
19038    .next()
19039    .await
19040    .unwrap();
19041    cx.run_until_parked();
19042
19043    cx.update_editor(|editor, window, cx| {
19044        editor.context_menu_next(&Default::default(), window, cx);
19045    });
19046    cx.run_until_parked();
19047
19048    cx.update_editor(|editor, _, _| {
19049        let context_menu = editor.context_menu.borrow_mut();
19050        let context_menu = context_menu
19051            .as_ref()
19052            .expect("Should have the context menu deployed");
19053        match context_menu {
19054            CodeContextMenu::Completions(completions_menu) => {
19055                let completions = completions_menu.completions.borrow_mut();
19056                assert_eq!(
19057                    completions
19058                        .iter()
19059                        .map(|completion| &completion.label.text)
19060                        .collect::<Vec<_>>(),
19061                    vec!["method id() Now resolved!", "other"],
19062                    "Should update first completion label, but not second as the filter text did not match."
19063                );
19064            }
19065            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
19066        }
19067    });
19068}
19069
19070#[gpui::test]
19071async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
19072    init_test(cx, |_| {});
19073    let mut cx = EditorLspTestContext::new_rust(
19074        lsp::ServerCapabilities {
19075            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
19076            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
19077            completion_provider: Some(lsp::CompletionOptions {
19078                resolve_provider: Some(true),
19079                ..Default::default()
19080            }),
19081            ..Default::default()
19082        },
19083        cx,
19084    )
19085    .await;
19086    cx.set_state(indoc! {"
19087        struct TestStruct {
19088            field: i32
19089        }
19090
19091        fn mainˇ() {
19092            let unused_var = 42;
19093            let test_struct = TestStruct { field: 42 };
19094        }
19095    "});
19096    let symbol_range = cx.lsp_range(indoc! {"
19097        struct TestStruct {
19098            field: i32
19099        }
19100
19101        «fn main»() {
19102            let unused_var = 42;
19103            let test_struct = TestStruct { field: 42 };
19104        }
19105    "});
19106    let mut hover_requests =
19107        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
19108            Ok(Some(lsp::Hover {
19109                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
19110                    kind: lsp::MarkupKind::Markdown,
19111                    value: "Function documentation".to_string(),
19112                }),
19113                range: Some(symbol_range),
19114            }))
19115        });
19116
19117    // Case 1: Test that code action menu hide hover popover
19118    cx.dispatch_action(Hover);
19119    hover_requests.next().await;
19120    cx.condition(|editor, _| editor.hover_state.visible()).await;
19121    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
19122        move |_, _, _| async move {
19123            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
19124                lsp::CodeAction {
19125                    title: "Remove unused variable".to_string(),
19126                    kind: Some(CodeActionKind::QUICKFIX),
19127                    edit: Some(lsp::WorkspaceEdit {
19128                        changes: Some(
19129                            [(
19130                                lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
19131                                vec![lsp::TextEdit {
19132                                    range: lsp::Range::new(
19133                                        lsp::Position::new(5, 4),
19134                                        lsp::Position::new(5, 27),
19135                                    ),
19136                                    new_text: "".to_string(),
19137                                }],
19138                            )]
19139                            .into_iter()
19140                            .collect(),
19141                        ),
19142                        ..Default::default()
19143                    }),
19144                    ..Default::default()
19145                },
19146            )]))
19147        },
19148    );
19149    cx.update_editor(|editor, window, cx| {
19150        editor.toggle_code_actions(
19151            &ToggleCodeActions {
19152                deployed_from: None,
19153                quick_launch: false,
19154            },
19155            window,
19156            cx,
19157        );
19158    });
19159    code_action_requests.next().await;
19160    cx.run_until_parked();
19161    cx.condition(|editor, _| editor.context_menu_visible())
19162        .await;
19163    cx.update_editor(|editor, _, _| {
19164        assert!(
19165            !editor.hover_state.visible(),
19166            "Hover popover should be hidden when code action menu is shown"
19167        );
19168        // Hide code actions
19169        editor.context_menu.take();
19170    });
19171
19172    // Case 2: Test that code completions hide hover popover
19173    cx.dispatch_action(Hover);
19174    hover_requests.next().await;
19175    cx.condition(|editor, _| editor.hover_state.visible()).await;
19176    let counter = Arc::new(AtomicUsize::new(0));
19177    let mut completion_requests =
19178        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
19179            let counter = counter.clone();
19180            async move {
19181                counter.fetch_add(1, atomic::Ordering::Release);
19182                Ok(Some(lsp::CompletionResponse::Array(vec![
19183                    lsp::CompletionItem {
19184                        label: "main".into(),
19185                        kind: Some(lsp::CompletionItemKind::FUNCTION),
19186                        detail: Some("() -> ()".to_string()),
19187                        ..Default::default()
19188                    },
19189                    lsp::CompletionItem {
19190                        label: "TestStruct".into(),
19191                        kind: Some(lsp::CompletionItemKind::STRUCT),
19192                        detail: Some("struct TestStruct".to_string()),
19193                        ..Default::default()
19194                    },
19195                ])))
19196            }
19197        });
19198    cx.update_editor(|editor, window, cx| {
19199        editor.show_completions(&ShowCompletions, window, cx);
19200    });
19201    completion_requests.next().await;
19202    cx.condition(|editor, _| editor.context_menu_visible())
19203        .await;
19204    cx.update_editor(|editor, _, _| {
19205        assert!(
19206            !editor.hover_state.visible(),
19207            "Hover popover should be hidden when completion menu is shown"
19208        );
19209    });
19210}
19211
19212#[gpui::test]
19213async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
19214    init_test(cx, |_| {});
19215
19216    let mut cx = EditorLspTestContext::new_rust(
19217        lsp::ServerCapabilities {
19218            completion_provider: Some(lsp::CompletionOptions {
19219                trigger_characters: Some(vec![".".to_string()]),
19220                resolve_provider: Some(true),
19221                ..Default::default()
19222            }),
19223            ..Default::default()
19224        },
19225        cx,
19226    )
19227    .await;
19228
19229    cx.set_state("fn main() { let a = 2ˇ; }");
19230    cx.simulate_keystroke(".");
19231
19232    let unresolved_item_1 = lsp::CompletionItem {
19233        label: "id".to_string(),
19234        filter_text: Some("id".to_string()),
19235        detail: None,
19236        documentation: None,
19237        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
19238            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
19239            new_text: ".id".to_string(),
19240        })),
19241        ..lsp::CompletionItem::default()
19242    };
19243    let resolved_item_1 = lsp::CompletionItem {
19244        additional_text_edits: Some(vec![lsp::TextEdit {
19245            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
19246            new_text: "!!".to_string(),
19247        }]),
19248        ..unresolved_item_1.clone()
19249    };
19250    let unresolved_item_2 = lsp::CompletionItem {
19251        label: "other".to_string(),
19252        filter_text: Some("other".to_string()),
19253        detail: None,
19254        documentation: None,
19255        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
19256            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
19257            new_text: ".other".to_string(),
19258        })),
19259        ..lsp::CompletionItem::default()
19260    };
19261    let resolved_item_2 = lsp::CompletionItem {
19262        additional_text_edits: Some(vec![lsp::TextEdit {
19263            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
19264            new_text: "??".to_string(),
19265        }]),
19266        ..unresolved_item_2.clone()
19267    };
19268
19269    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
19270    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
19271    cx.lsp
19272        .server
19273        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
19274            let unresolved_item_1 = unresolved_item_1.clone();
19275            let resolved_item_1 = resolved_item_1.clone();
19276            let unresolved_item_2 = unresolved_item_2.clone();
19277            let resolved_item_2 = resolved_item_2.clone();
19278            let resolve_requests_1 = resolve_requests_1.clone();
19279            let resolve_requests_2 = resolve_requests_2.clone();
19280            move |unresolved_request, _| {
19281                let unresolved_item_1 = unresolved_item_1.clone();
19282                let resolved_item_1 = resolved_item_1.clone();
19283                let unresolved_item_2 = unresolved_item_2.clone();
19284                let resolved_item_2 = resolved_item_2.clone();
19285                let resolve_requests_1 = resolve_requests_1.clone();
19286                let resolve_requests_2 = resolve_requests_2.clone();
19287                async move {
19288                    if unresolved_request == unresolved_item_1 {
19289                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
19290                        Ok(resolved_item_1.clone())
19291                    } else if unresolved_request == unresolved_item_2 {
19292                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
19293                        Ok(resolved_item_2.clone())
19294                    } else {
19295                        panic!("Unexpected completion item {unresolved_request:?}")
19296                    }
19297                }
19298            }
19299        })
19300        .detach();
19301
19302    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
19303        let unresolved_item_1 = unresolved_item_1.clone();
19304        let unresolved_item_2 = unresolved_item_2.clone();
19305        async move {
19306            Ok(Some(lsp::CompletionResponse::Array(vec![
19307                unresolved_item_1,
19308                unresolved_item_2,
19309            ])))
19310        }
19311    })
19312    .next()
19313    .await;
19314
19315    cx.condition(|editor, _| editor.context_menu_visible())
19316        .await;
19317    cx.update_editor(|editor, _, _| {
19318        let context_menu = editor.context_menu.borrow_mut();
19319        let context_menu = context_menu
19320            .as_ref()
19321            .expect("Should have the context menu deployed");
19322        match context_menu {
19323            CodeContextMenu::Completions(completions_menu) => {
19324                let completions = completions_menu.completions.borrow_mut();
19325                assert_eq!(
19326                    completions
19327                        .iter()
19328                        .map(|completion| &completion.label.text)
19329                        .collect::<Vec<_>>(),
19330                    vec!["id", "other"]
19331                )
19332            }
19333            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
19334        }
19335    });
19336    cx.run_until_parked();
19337
19338    cx.update_editor(|editor, window, cx| {
19339        editor.context_menu_next(&ContextMenuNext, window, cx);
19340    });
19341    cx.run_until_parked();
19342    cx.update_editor(|editor, window, cx| {
19343        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
19344    });
19345    cx.run_until_parked();
19346    cx.update_editor(|editor, window, cx| {
19347        editor.context_menu_next(&ContextMenuNext, window, cx);
19348    });
19349    cx.run_until_parked();
19350    cx.update_editor(|editor, window, cx| {
19351        editor
19352            .compose_completion(&ComposeCompletion::default(), window, cx)
19353            .expect("No task returned")
19354    })
19355    .await
19356    .expect("Completion failed");
19357    cx.run_until_parked();
19358
19359    cx.update_editor(|editor, _, cx| {
19360        assert_eq!(
19361            resolve_requests_1.load(atomic::Ordering::Acquire),
19362            1,
19363            "Should always resolve once despite multiple selections"
19364        );
19365        assert_eq!(
19366            resolve_requests_2.load(atomic::Ordering::Acquire),
19367            1,
19368            "Should always resolve once after multiple selections and applying the completion"
19369        );
19370        assert_eq!(
19371            editor.text(cx),
19372            "fn main() { let a = ??.other; }",
19373            "Should use resolved data when applying the completion"
19374        );
19375    });
19376}
19377
19378#[gpui::test]
19379async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
19380    init_test(cx, |_| {});
19381
19382    let item_0 = lsp::CompletionItem {
19383        label: "abs".into(),
19384        insert_text: Some("abs".into()),
19385        data: Some(json!({ "very": "special"})),
19386        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
19387        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
19388            lsp::InsertReplaceEdit {
19389                new_text: "abs".to_string(),
19390                insert: lsp::Range::default(),
19391                replace: lsp::Range::default(),
19392            },
19393        )),
19394        ..lsp::CompletionItem::default()
19395    };
19396    let items = iter::once(item_0.clone())
19397        .chain((11..51).map(|i| lsp::CompletionItem {
19398            label: format!("item_{}", i),
19399            insert_text: Some(format!("item_{}", i)),
19400            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
19401            ..lsp::CompletionItem::default()
19402        }))
19403        .collect::<Vec<_>>();
19404
19405    let default_commit_characters = vec!["?".to_string()];
19406    let default_data = json!({ "default": "data"});
19407    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
19408    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
19409    let default_edit_range = lsp::Range {
19410        start: lsp::Position {
19411            line: 0,
19412            character: 5,
19413        },
19414        end: lsp::Position {
19415            line: 0,
19416            character: 5,
19417        },
19418    };
19419
19420    let mut cx = EditorLspTestContext::new_rust(
19421        lsp::ServerCapabilities {
19422            completion_provider: Some(lsp::CompletionOptions {
19423                trigger_characters: Some(vec![".".to_string()]),
19424                resolve_provider: Some(true),
19425                ..Default::default()
19426            }),
19427            ..Default::default()
19428        },
19429        cx,
19430    )
19431    .await;
19432
19433    cx.set_state("fn main() { let a = 2ˇ; }");
19434    cx.simulate_keystroke(".");
19435
19436    let completion_data = default_data.clone();
19437    let completion_characters = default_commit_characters.clone();
19438    let completion_items = items.clone();
19439    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
19440        let default_data = completion_data.clone();
19441        let default_commit_characters = completion_characters.clone();
19442        let items = completion_items.clone();
19443        async move {
19444            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
19445                items,
19446                item_defaults: Some(lsp::CompletionListItemDefaults {
19447                    data: Some(default_data.clone()),
19448                    commit_characters: Some(default_commit_characters.clone()),
19449                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
19450                        default_edit_range,
19451                    )),
19452                    insert_text_format: Some(default_insert_text_format),
19453                    insert_text_mode: Some(default_insert_text_mode),
19454                }),
19455                ..lsp::CompletionList::default()
19456            })))
19457        }
19458    })
19459    .next()
19460    .await;
19461
19462    let resolved_items = Arc::new(Mutex::new(Vec::new()));
19463    cx.lsp
19464        .server
19465        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
19466            let closure_resolved_items = resolved_items.clone();
19467            move |item_to_resolve, _| {
19468                let closure_resolved_items = closure_resolved_items.clone();
19469                async move {
19470                    closure_resolved_items.lock().push(item_to_resolve.clone());
19471                    Ok(item_to_resolve)
19472                }
19473            }
19474        })
19475        .detach();
19476
19477    cx.condition(|editor, _| editor.context_menu_visible())
19478        .await;
19479    cx.run_until_parked();
19480    cx.update_editor(|editor, _, _| {
19481        let menu = editor.context_menu.borrow_mut();
19482        match menu.as_ref().expect("should have the completions menu") {
19483            CodeContextMenu::Completions(completions_menu) => {
19484                assert_eq!(
19485                    completions_menu
19486                        .entries
19487                        .borrow()
19488                        .iter()
19489                        .map(|mat| mat.string.clone())
19490                        .collect::<Vec<String>>(),
19491                    items
19492                        .iter()
19493                        .map(|completion| completion.label.clone())
19494                        .collect::<Vec<String>>()
19495                );
19496            }
19497            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
19498        }
19499    });
19500    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
19501    // with 4 from the end.
19502    assert_eq!(
19503        *resolved_items.lock(),
19504        [&items[0..16], &items[items.len() - 4..items.len()]]
19505            .concat()
19506            .iter()
19507            .cloned()
19508            .map(|mut item| {
19509                if item.data.is_none() {
19510                    item.data = Some(default_data.clone());
19511                }
19512                item
19513            })
19514            .collect::<Vec<lsp::CompletionItem>>(),
19515        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
19516    );
19517    resolved_items.lock().clear();
19518
19519    cx.update_editor(|editor, window, cx| {
19520        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
19521    });
19522    cx.run_until_parked();
19523    // Completions that have already been resolved are skipped.
19524    assert_eq!(
19525        *resolved_items.lock(),
19526        items[items.len() - 17..items.len() - 4]
19527            .iter()
19528            .cloned()
19529            .map(|mut item| {
19530                if item.data.is_none() {
19531                    item.data = Some(default_data.clone());
19532                }
19533                item
19534            })
19535            .collect::<Vec<lsp::CompletionItem>>()
19536    );
19537    resolved_items.lock().clear();
19538}
19539
19540#[gpui::test]
19541async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
19542    init_test(cx, |_| {});
19543
19544    let mut cx = EditorLspTestContext::new(
19545        Language::new(
19546            LanguageConfig {
19547                matcher: LanguageMatcher {
19548                    path_suffixes: vec!["jsx".into()],
19549                    ..Default::default()
19550                },
19551                overrides: [(
19552                    "element".into(),
19553                    LanguageConfigOverride {
19554                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
19555                        ..Default::default()
19556                    },
19557                )]
19558                .into_iter()
19559                .collect(),
19560                ..Default::default()
19561            },
19562            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
19563        )
19564        .with_override_query("(jsx_self_closing_element) @element")
19565        .unwrap(),
19566        lsp::ServerCapabilities {
19567            completion_provider: Some(lsp::CompletionOptions {
19568                trigger_characters: Some(vec![":".to_string()]),
19569                ..Default::default()
19570            }),
19571            ..Default::default()
19572        },
19573        cx,
19574    )
19575    .await;
19576
19577    cx.lsp
19578        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
19579            Ok(Some(lsp::CompletionResponse::Array(vec![
19580                lsp::CompletionItem {
19581                    label: "bg-blue".into(),
19582                    ..Default::default()
19583                },
19584                lsp::CompletionItem {
19585                    label: "bg-red".into(),
19586                    ..Default::default()
19587                },
19588                lsp::CompletionItem {
19589                    label: "bg-yellow".into(),
19590                    ..Default::default()
19591                },
19592            ])))
19593        });
19594
19595    cx.set_state(r#"<p class="bgˇ" />"#);
19596
19597    // Trigger completion when typing a dash, because the dash is an extra
19598    // word character in the 'element' scope, which contains the cursor.
19599    cx.simulate_keystroke("-");
19600    cx.executor().run_until_parked();
19601    cx.update_editor(|editor, _, _| {
19602        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
19603        {
19604            assert_eq!(
19605                completion_menu_entries(menu),
19606                &["bg-blue", "bg-red", "bg-yellow"]
19607            );
19608        } else {
19609            panic!("expected completion menu to be open");
19610        }
19611    });
19612
19613    cx.simulate_keystroke("l");
19614    cx.executor().run_until_parked();
19615    cx.update_editor(|editor, _, _| {
19616        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
19617        {
19618            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
19619        } else {
19620            panic!("expected completion menu to be open");
19621        }
19622    });
19623
19624    // When filtering completions, consider the character after the '-' to
19625    // be the start of a subword.
19626    cx.set_state(r#"<p class="yelˇ" />"#);
19627    cx.simulate_keystroke("l");
19628    cx.executor().run_until_parked();
19629    cx.update_editor(|editor, _, _| {
19630        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
19631        {
19632            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
19633        } else {
19634            panic!("expected completion menu to be open");
19635        }
19636    });
19637}
19638
19639fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
19640    let entries = menu.entries.borrow();
19641    entries.iter().map(|mat| mat.string.clone()).collect()
19642}
19643
19644#[gpui::test]
19645async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
19646    init_test(cx, |settings| {
19647        settings.defaults.formatter = Some(FormatterList::Single(Formatter::Prettier))
19648    });
19649
19650    let fs = FakeFs::new(cx.executor());
19651    fs.insert_file(path!("/file.ts"), Default::default()).await;
19652
19653    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
19654    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
19655
19656    language_registry.add(Arc::new(Language::new(
19657        LanguageConfig {
19658            name: "TypeScript".into(),
19659            matcher: LanguageMatcher {
19660                path_suffixes: vec!["ts".to_string()],
19661                ..Default::default()
19662            },
19663            ..Default::default()
19664        },
19665        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
19666    )));
19667    update_test_language_settings(cx, |settings| {
19668        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
19669    });
19670
19671    let test_plugin = "test_plugin";
19672    let _ = language_registry.register_fake_lsp(
19673        "TypeScript",
19674        FakeLspAdapter {
19675            prettier_plugins: vec![test_plugin],
19676            ..Default::default()
19677        },
19678    );
19679
19680    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
19681    let buffer = project
19682        .update(cx, |project, cx| {
19683            project.open_local_buffer(path!("/file.ts"), cx)
19684        })
19685        .await
19686        .unwrap();
19687
19688    let buffer_text = "one\ntwo\nthree\n";
19689    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
19690    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
19691    editor.update_in(cx, |editor, window, cx| {
19692        editor.set_text(buffer_text, window, cx)
19693    });
19694
19695    editor
19696        .update_in(cx, |editor, window, cx| {
19697            editor.perform_format(
19698                project.clone(),
19699                FormatTrigger::Manual,
19700                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
19701                window,
19702                cx,
19703            )
19704        })
19705        .unwrap()
19706        .await;
19707    assert_eq!(
19708        editor.update(cx, |editor, cx| editor.text(cx)),
19709        buffer_text.to_string() + prettier_format_suffix,
19710        "Test prettier formatting was not applied to the original buffer text",
19711    );
19712
19713    update_test_language_settings(cx, |settings| {
19714        settings.defaults.formatter = Some(FormatterList::default())
19715    });
19716    let format = editor.update_in(cx, |editor, window, cx| {
19717        editor.perform_format(
19718            project.clone(),
19719            FormatTrigger::Manual,
19720            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
19721            window,
19722            cx,
19723        )
19724    });
19725    format.await.unwrap();
19726    assert_eq!(
19727        editor.update(cx, |editor, cx| editor.text(cx)),
19728        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
19729        "Autoformatting (via test prettier) was not applied to the original buffer text",
19730    );
19731}
19732
19733#[gpui::test]
19734async fn test_document_format_with_prettier_explicit_language(cx: &mut TestAppContext) {
19735    init_test(cx, |settings| {
19736        settings.defaults.formatter = Some(FormatterList::Single(Formatter::Prettier))
19737    });
19738
19739    let fs = FakeFs::new(cx.executor());
19740    fs.insert_file(path!("/file.settings"), Default::default())
19741        .await;
19742
19743    let project = Project::test(fs, [path!("/file.settings").as_ref()], cx).await;
19744    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
19745
19746    let ts_lang = Arc::new(Language::new(
19747        LanguageConfig {
19748            name: "TypeScript".into(),
19749            matcher: LanguageMatcher {
19750                path_suffixes: vec!["ts".to_string()],
19751                ..LanguageMatcher::default()
19752            },
19753            prettier_parser_name: Some("typescript".to_string()),
19754            ..LanguageConfig::default()
19755        },
19756        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
19757    ));
19758
19759    language_registry.add(ts_lang.clone());
19760
19761    update_test_language_settings(cx, |settings| {
19762        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
19763    });
19764
19765    let test_plugin = "test_plugin";
19766    let _ = language_registry.register_fake_lsp(
19767        "TypeScript",
19768        FakeLspAdapter {
19769            prettier_plugins: vec![test_plugin],
19770            ..Default::default()
19771        },
19772    );
19773
19774    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
19775    let buffer = project
19776        .update(cx, |project, cx| {
19777            project.open_local_buffer(path!("/file.settings"), cx)
19778        })
19779        .await
19780        .unwrap();
19781
19782    project.update(cx, |project, cx| {
19783        project.set_language_for_buffer(&buffer, ts_lang, cx)
19784    });
19785
19786    let buffer_text = "one\ntwo\nthree\n";
19787    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
19788    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
19789    editor.update_in(cx, |editor, window, cx| {
19790        editor.set_text(buffer_text, window, cx)
19791    });
19792
19793    editor
19794        .update_in(cx, |editor, window, cx| {
19795            editor.perform_format(
19796                project.clone(),
19797                FormatTrigger::Manual,
19798                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
19799                window,
19800                cx,
19801            )
19802        })
19803        .unwrap()
19804        .await;
19805    assert_eq!(
19806        editor.update(cx, |editor, cx| editor.text(cx)),
19807        buffer_text.to_string() + prettier_format_suffix + "\ntypescript",
19808        "Test prettier formatting was not applied to the original buffer text",
19809    );
19810
19811    update_test_language_settings(cx, |settings| {
19812        settings.defaults.formatter = Some(FormatterList::default())
19813    });
19814    let format = editor.update_in(cx, |editor, window, cx| {
19815        editor.perform_format(
19816            project.clone(),
19817            FormatTrigger::Manual,
19818            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
19819            window,
19820            cx,
19821        )
19822    });
19823    format.await.unwrap();
19824
19825    assert_eq!(
19826        editor.update(cx, |editor, cx| editor.text(cx)),
19827        buffer_text.to_string()
19828            + prettier_format_suffix
19829            + "\ntypescript\n"
19830            + prettier_format_suffix
19831            + "\ntypescript",
19832        "Autoformatting (via test prettier) was not applied to the original buffer text",
19833    );
19834}
19835
19836#[gpui::test]
19837async fn test_addition_reverts(cx: &mut TestAppContext) {
19838    init_test(cx, |_| {});
19839    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
19840    let base_text = indoc! {r#"
19841        struct Row;
19842        struct Row1;
19843        struct Row2;
19844
19845        struct Row4;
19846        struct Row5;
19847        struct Row6;
19848
19849        struct Row8;
19850        struct Row9;
19851        struct Row10;"#};
19852
19853    // When addition hunks are not adjacent to carets, no hunk revert is performed
19854    assert_hunk_revert(
19855        indoc! {r#"struct Row;
19856                   struct Row1;
19857                   struct Row1.1;
19858                   struct Row1.2;
19859                   struct Row2;ˇ
19860
19861                   struct Row4;
19862                   struct Row5;
19863                   struct Row6;
19864
19865                   struct Row8;
19866                   ˇstruct Row9;
19867                   struct Row9.1;
19868                   struct Row9.2;
19869                   struct Row9.3;
19870                   struct Row10;"#},
19871        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
19872        indoc! {r#"struct Row;
19873                   struct Row1;
19874                   struct Row1.1;
19875                   struct Row1.2;
19876                   struct Row2;ˇ
19877
19878                   struct Row4;
19879                   struct Row5;
19880                   struct Row6;
19881
19882                   struct Row8;
19883                   ˇstruct Row9;
19884                   struct Row9.1;
19885                   struct Row9.2;
19886                   struct Row9.3;
19887                   struct Row10;"#},
19888        base_text,
19889        &mut cx,
19890    );
19891    // Same for selections
19892    assert_hunk_revert(
19893        indoc! {r#"struct Row;
19894                   struct Row1;
19895                   struct Row2;
19896                   struct Row2.1;
19897                   struct Row2.2;
19898                   «ˇ
19899                   struct Row4;
19900                   struct» Row5;
19901                   «struct Row6;
19902                   ˇ»
19903                   struct Row9.1;
19904                   struct Row9.2;
19905                   struct Row9.3;
19906                   struct Row8;
19907                   struct Row9;
19908                   struct Row10;"#},
19909        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
19910        indoc! {r#"struct Row;
19911                   struct Row1;
19912                   struct Row2;
19913                   struct Row2.1;
19914                   struct Row2.2;
19915                   «ˇ
19916                   struct Row4;
19917                   struct» Row5;
19918                   «struct Row6;
19919                   ˇ»
19920                   struct Row9.1;
19921                   struct Row9.2;
19922                   struct Row9.3;
19923                   struct Row8;
19924                   struct Row9;
19925                   struct Row10;"#},
19926        base_text,
19927        &mut cx,
19928    );
19929
19930    // When carets and selections intersect the addition hunks, those are reverted.
19931    // Adjacent carets got merged.
19932    assert_hunk_revert(
19933        indoc! {r#"struct Row;
19934                   ˇ// something on the top
19935                   struct Row1;
19936                   struct Row2;
19937                   struct Roˇw3.1;
19938                   struct Row2.2;
19939                   struct Row2.3;ˇ
19940
19941                   struct Row4;
19942                   struct ˇRow5.1;
19943                   struct Row5.2;
19944                   struct «Rowˇ»5.3;
19945                   struct Row5;
19946                   struct Row6;
19947                   ˇ
19948                   struct Row9.1;
19949                   struct «Rowˇ»9.2;
19950                   struct «ˇRow»9.3;
19951                   struct Row8;
19952                   struct Row9;
19953                   «ˇ// something on bottom»
19954                   struct Row10;"#},
19955        vec![
19956            DiffHunkStatusKind::Added,
19957            DiffHunkStatusKind::Added,
19958            DiffHunkStatusKind::Added,
19959            DiffHunkStatusKind::Added,
19960            DiffHunkStatusKind::Added,
19961        ],
19962        indoc! {r#"struct Row;
19963                   ˇstruct Row1;
19964                   struct Row2;
19965                   ˇ
19966                   struct Row4;
19967                   ˇstruct Row5;
19968                   struct Row6;
19969                   ˇ
19970                   ˇstruct Row8;
19971                   struct Row9;
19972                   ˇstruct Row10;"#},
19973        base_text,
19974        &mut cx,
19975    );
19976}
19977
19978#[gpui::test]
19979async fn test_modification_reverts(cx: &mut TestAppContext) {
19980    init_test(cx, |_| {});
19981    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
19982    let base_text = indoc! {r#"
19983        struct Row;
19984        struct Row1;
19985        struct Row2;
19986
19987        struct Row4;
19988        struct Row5;
19989        struct Row6;
19990
19991        struct Row8;
19992        struct Row9;
19993        struct Row10;"#};
19994
19995    // Modification hunks behave the same as the addition ones.
19996    assert_hunk_revert(
19997        indoc! {r#"struct Row;
19998                   struct Row1;
19999                   struct Row33;
20000                   ˇ
20001                   struct Row4;
20002                   struct Row5;
20003                   struct Row6;
20004                   ˇ
20005                   struct Row99;
20006                   struct Row9;
20007                   struct Row10;"#},
20008        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
20009        indoc! {r#"struct Row;
20010                   struct Row1;
20011                   struct Row33;
20012                   ˇ
20013                   struct Row4;
20014                   struct Row5;
20015                   struct Row6;
20016                   ˇ
20017                   struct Row99;
20018                   struct Row9;
20019                   struct Row10;"#},
20020        base_text,
20021        &mut cx,
20022    );
20023    assert_hunk_revert(
20024        indoc! {r#"struct Row;
20025                   struct Row1;
20026                   struct Row33;
20027                   «ˇ
20028                   struct Row4;
20029                   struct» Row5;
20030                   «struct Row6;
20031                   ˇ»
20032                   struct Row99;
20033                   struct Row9;
20034                   struct Row10;"#},
20035        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
20036        indoc! {r#"struct Row;
20037                   struct Row1;
20038                   struct Row33;
20039                   «ˇ
20040                   struct Row4;
20041                   struct» Row5;
20042                   «struct Row6;
20043                   ˇ»
20044                   struct Row99;
20045                   struct Row9;
20046                   struct Row10;"#},
20047        base_text,
20048        &mut cx,
20049    );
20050
20051    assert_hunk_revert(
20052        indoc! {r#"ˇstruct Row1.1;
20053                   struct Row1;
20054                   «ˇstr»uct Row22;
20055
20056                   struct ˇRow44;
20057                   struct Row5;
20058                   struct «Rˇ»ow66;ˇ
20059
20060                   «struˇ»ct Row88;
20061                   struct Row9;
20062                   struct Row1011;ˇ"#},
20063        vec![
20064            DiffHunkStatusKind::Modified,
20065            DiffHunkStatusKind::Modified,
20066            DiffHunkStatusKind::Modified,
20067            DiffHunkStatusKind::Modified,
20068            DiffHunkStatusKind::Modified,
20069            DiffHunkStatusKind::Modified,
20070        ],
20071        indoc! {r#"struct Row;
20072                   ˇstruct Row1;
20073                   struct Row2;
20074                   ˇ
20075                   struct Row4;
20076                   ˇstruct Row5;
20077                   struct Row6;
20078                   ˇ
20079                   struct Row8;
20080                   ˇstruct Row9;
20081                   struct Row10;ˇ"#},
20082        base_text,
20083        &mut cx,
20084    );
20085}
20086
20087#[gpui::test]
20088async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
20089    init_test(cx, |_| {});
20090    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
20091    let base_text = indoc! {r#"
20092        one
20093
20094        two
20095        three
20096        "#};
20097
20098    cx.set_head_text(base_text);
20099    cx.set_state("\nˇ\n");
20100    cx.executor().run_until_parked();
20101    cx.update_editor(|editor, _window, cx| {
20102        editor.expand_selected_diff_hunks(cx);
20103    });
20104    cx.executor().run_until_parked();
20105    cx.update_editor(|editor, window, cx| {
20106        editor.backspace(&Default::default(), window, cx);
20107    });
20108    cx.run_until_parked();
20109    cx.assert_state_with_diff(
20110        indoc! {r#"
20111
20112        - two
20113        - threeˇ
20114        +
20115        "#}
20116        .to_string(),
20117    );
20118}
20119
20120#[gpui::test]
20121async fn test_deletion_reverts(cx: &mut TestAppContext) {
20122    init_test(cx, |_| {});
20123    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
20124    let base_text = indoc! {r#"struct Row;
20125struct Row1;
20126struct Row2;
20127
20128struct Row4;
20129struct Row5;
20130struct Row6;
20131
20132struct Row8;
20133struct Row9;
20134struct Row10;"#};
20135
20136    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
20137    assert_hunk_revert(
20138        indoc! {r#"struct Row;
20139                   struct Row2;
20140
20141                   ˇstruct Row4;
20142                   struct Row5;
20143                   struct Row6;
20144                   ˇ
20145                   struct Row8;
20146                   struct Row10;"#},
20147        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
20148        indoc! {r#"struct Row;
20149                   struct Row2;
20150
20151                   ˇstruct Row4;
20152                   struct Row5;
20153                   struct Row6;
20154                   ˇ
20155                   struct Row8;
20156                   struct Row10;"#},
20157        base_text,
20158        &mut cx,
20159    );
20160    assert_hunk_revert(
20161        indoc! {r#"struct Row;
20162                   struct Row2;
20163
20164                   «ˇstruct Row4;
20165                   struct» Row5;
20166                   «struct Row6;
20167                   ˇ»
20168                   struct Row8;
20169                   struct Row10;"#},
20170        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
20171        indoc! {r#"struct Row;
20172                   struct Row2;
20173
20174                   «ˇstruct Row4;
20175                   struct» Row5;
20176                   «struct Row6;
20177                   ˇ»
20178                   struct Row8;
20179                   struct Row10;"#},
20180        base_text,
20181        &mut cx,
20182    );
20183
20184    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
20185    assert_hunk_revert(
20186        indoc! {r#"struct Row;
20187                   ˇstruct Row2;
20188
20189                   struct Row4;
20190                   struct Row5;
20191                   struct Row6;
20192
20193                   struct Row8;ˇ
20194                   struct Row10;"#},
20195        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
20196        indoc! {r#"struct Row;
20197                   struct Row1;
20198                   ˇstruct Row2;
20199
20200                   struct Row4;
20201                   struct Row5;
20202                   struct Row6;
20203
20204                   struct Row8;ˇ
20205                   struct Row9;
20206                   struct Row10;"#},
20207        base_text,
20208        &mut cx,
20209    );
20210    assert_hunk_revert(
20211        indoc! {r#"struct Row;
20212                   struct Row2«ˇ;
20213                   struct Row4;
20214                   struct» Row5;
20215                   «struct Row6;
20216
20217                   struct Row8;ˇ»
20218                   struct Row10;"#},
20219        vec![
20220            DiffHunkStatusKind::Deleted,
20221            DiffHunkStatusKind::Deleted,
20222            DiffHunkStatusKind::Deleted,
20223        ],
20224        indoc! {r#"struct Row;
20225                   struct Row1;
20226                   struct Row2«ˇ;
20227
20228                   struct Row4;
20229                   struct» Row5;
20230                   «struct Row6;
20231
20232                   struct Row8;ˇ»
20233                   struct Row9;
20234                   struct Row10;"#},
20235        base_text,
20236        &mut cx,
20237    );
20238}
20239
20240#[gpui::test]
20241async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
20242    init_test(cx, |_| {});
20243
20244    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
20245    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
20246    let base_text_3 =
20247        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
20248
20249    let text_1 = edit_first_char_of_every_line(base_text_1);
20250    let text_2 = edit_first_char_of_every_line(base_text_2);
20251    let text_3 = edit_first_char_of_every_line(base_text_3);
20252
20253    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
20254    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
20255    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
20256
20257    let multibuffer = cx.new(|cx| {
20258        let mut multibuffer = MultiBuffer::new(ReadWrite);
20259        multibuffer.push_excerpts(
20260            buffer_1.clone(),
20261            [
20262                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20263                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20264                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20265            ],
20266            cx,
20267        );
20268        multibuffer.push_excerpts(
20269            buffer_2.clone(),
20270            [
20271                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20272                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20273                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20274            ],
20275            cx,
20276        );
20277        multibuffer.push_excerpts(
20278            buffer_3.clone(),
20279            [
20280                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20281                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20282                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20283            ],
20284            cx,
20285        );
20286        multibuffer
20287    });
20288
20289    let fs = FakeFs::new(cx.executor());
20290    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
20291    let (editor, cx) = cx
20292        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
20293    editor.update_in(cx, |editor, _window, cx| {
20294        for (buffer, diff_base) in [
20295            (buffer_1.clone(), base_text_1),
20296            (buffer_2.clone(), base_text_2),
20297            (buffer_3.clone(), base_text_3),
20298        ] {
20299            let diff = cx.new(|cx| {
20300                BufferDiff::new_with_base_text(diff_base, &buffer.read(cx).text_snapshot(), cx)
20301            });
20302            editor
20303                .buffer
20304                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
20305        }
20306    });
20307    cx.executor().run_until_parked();
20308
20309    editor.update_in(cx, |editor, window, cx| {
20310        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}");
20311        editor.select_all(&SelectAll, window, cx);
20312        editor.git_restore(&Default::default(), window, cx);
20313    });
20314    cx.executor().run_until_parked();
20315
20316    // When all ranges are selected, all buffer hunks are reverted.
20317    editor.update(cx, |editor, cx| {
20318        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");
20319    });
20320    buffer_1.update(cx, |buffer, _| {
20321        assert_eq!(buffer.text(), base_text_1);
20322    });
20323    buffer_2.update(cx, |buffer, _| {
20324        assert_eq!(buffer.text(), base_text_2);
20325    });
20326    buffer_3.update(cx, |buffer, _| {
20327        assert_eq!(buffer.text(), base_text_3);
20328    });
20329
20330    editor.update_in(cx, |editor, window, cx| {
20331        editor.undo(&Default::default(), window, cx);
20332    });
20333
20334    editor.update_in(cx, |editor, window, cx| {
20335        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20336            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
20337        });
20338        editor.git_restore(&Default::default(), window, cx);
20339    });
20340
20341    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
20342    // but not affect buffer_2 and its related excerpts.
20343    editor.update(cx, |editor, cx| {
20344        assert_eq!(
20345            editor.text(cx),
20346            "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}"
20347        );
20348    });
20349    buffer_1.update(cx, |buffer, _| {
20350        assert_eq!(buffer.text(), base_text_1);
20351    });
20352    buffer_2.update(cx, |buffer, _| {
20353        assert_eq!(
20354            buffer.text(),
20355            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
20356        );
20357    });
20358    buffer_3.update(cx, |buffer, _| {
20359        assert_eq!(
20360            buffer.text(),
20361            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
20362        );
20363    });
20364
20365    fn edit_first_char_of_every_line(text: &str) -> String {
20366        text.split('\n')
20367            .map(|line| format!("X{}", &line[1..]))
20368            .collect::<Vec<_>>()
20369            .join("\n")
20370    }
20371}
20372
20373#[gpui::test]
20374async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
20375    init_test(cx, |_| {});
20376
20377    let cols = 4;
20378    let rows = 10;
20379    let sample_text_1 = sample_text(rows, cols, 'a');
20380    assert_eq!(
20381        sample_text_1,
20382        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
20383    );
20384    let sample_text_2 = sample_text(rows, cols, 'l');
20385    assert_eq!(
20386        sample_text_2,
20387        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
20388    );
20389    let sample_text_3 = sample_text(rows, cols, 'v');
20390    assert_eq!(
20391        sample_text_3,
20392        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
20393    );
20394
20395    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
20396    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
20397    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
20398
20399    let multi_buffer = cx.new(|cx| {
20400        let mut multibuffer = MultiBuffer::new(ReadWrite);
20401        multibuffer.push_excerpts(
20402            buffer_1.clone(),
20403            [
20404                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20405                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20406                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20407            ],
20408            cx,
20409        );
20410        multibuffer.push_excerpts(
20411            buffer_2.clone(),
20412            [
20413                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20414                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20415                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20416            ],
20417            cx,
20418        );
20419        multibuffer.push_excerpts(
20420            buffer_3.clone(),
20421            [
20422                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20423                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20424                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20425            ],
20426            cx,
20427        );
20428        multibuffer
20429    });
20430
20431    let fs = FakeFs::new(cx.executor());
20432    fs.insert_tree(
20433        "/a",
20434        json!({
20435            "main.rs": sample_text_1,
20436            "other.rs": sample_text_2,
20437            "lib.rs": sample_text_3,
20438        }),
20439    )
20440    .await;
20441    let project = Project::test(fs, ["/a".as_ref()], cx).await;
20442    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20443    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20444    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
20445        Editor::new(
20446            EditorMode::full(),
20447            multi_buffer,
20448            Some(project.clone()),
20449            window,
20450            cx,
20451        )
20452    });
20453    let multibuffer_item_id = workspace
20454        .update(cx, |workspace, window, cx| {
20455            assert!(
20456                workspace.active_item(cx).is_none(),
20457                "active item should be None before the first item is added"
20458            );
20459            workspace.add_item_to_active_pane(
20460                Box::new(multi_buffer_editor.clone()),
20461                None,
20462                true,
20463                window,
20464                cx,
20465            );
20466            let active_item = workspace
20467                .active_item(cx)
20468                .expect("should have an active item after adding the multi buffer");
20469            assert_eq!(
20470                active_item.buffer_kind(cx),
20471                ItemBufferKind::Multibuffer,
20472                "A multi buffer was expected to active after adding"
20473            );
20474            active_item.item_id()
20475        })
20476        .unwrap();
20477    cx.executor().run_until_parked();
20478
20479    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20480        editor.change_selections(
20481            SelectionEffects::scroll(Autoscroll::Next),
20482            window,
20483            cx,
20484            |s| s.select_ranges(Some(MultiBufferOffset(1)..MultiBufferOffset(2))),
20485        );
20486        editor.open_excerpts(&OpenExcerpts, window, cx);
20487    });
20488    cx.executor().run_until_parked();
20489    let first_item_id = workspace
20490        .update(cx, |workspace, window, cx| {
20491            let active_item = workspace
20492                .active_item(cx)
20493                .expect("should have an active item after navigating into the 1st buffer");
20494            let first_item_id = active_item.item_id();
20495            assert_ne!(
20496                first_item_id, multibuffer_item_id,
20497                "Should navigate into the 1st buffer and activate it"
20498            );
20499            assert_eq!(
20500                active_item.buffer_kind(cx),
20501                ItemBufferKind::Singleton,
20502                "New active item should be a singleton buffer"
20503            );
20504            assert_eq!(
20505                active_item
20506                    .act_as::<Editor>(cx)
20507                    .expect("should have navigated into an editor for the 1st buffer")
20508                    .read(cx)
20509                    .text(cx),
20510                sample_text_1
20511            );
20512
20513            workspace
20514                .go_back(workspace.active_pane().downgrade(), window, cx)
20515                .detach_and_log_err(cx);
20516
20517            first_item_id
20518        })
20519        .unwrap();
20520    cx.executor().run_until_parked();
20521    workspace
20522        .update(cx, |workspace, _, cx| {
20523            let active_item = workspace
20524                .active_item(cx)
20525                .expect("should have an active item after navigating back");
20526            assert_eq!(
20527                active_item.item_id(),
20528                multibuffer_item_id,
20529                "Should navigate back to the multi buffer"
20530            );
20531            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
20532        })
20533        .unwrap();
20534
20535    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20536        editor.change_selections(
20537            SelectionEffects::scroll(Autoscroll::Next),
20538            window,
20539            cx,
20540            |s| s.select_ranges(Some(MultiBufferOffset(39)..MultiBufferOffset(40))),
20541        );
20542        editor.open_excerpts(&OpenExcerpts, window, cx);
20543    });
20544    cx.executor().run_until_parked();
20545    let second_item_id = workspace
20546        .update(cx, |workspace, window, cx| {
20547            let active_item = workspace
20548                .active_item(cx)
20549                .expect("should have an active item after navigating into the 2nd buffer");
20550            let second_item_id = active_item.item_id();
20551            assert_ne!(
20552                second_item_id, multibuffer_item_id,
20553                "Should navigate away from the multibuffer"
20554            );
20555            assert_ne!(
20556                second_item_id, first_item_id,
20557                "Should navigate into the 2nd buffer and activate it"
20558            );
20559            assert_eq!(
20560                active_item.buffer_kind(cx),
20561                ItemBufferKind::Singleton,
20562                "New active item should be a singleton buffer"
20563            );
20564            assert_eq!(
20565                active_item
20566                    .act_as::<Editor>(cx)
20567                    .expect("should have navigated into an editor")
20568                    .read(cx)
20569                    .text(cx),
20570                sample_text_2
20571            );
20572
20573            workspace
20574                .go_back(workspace.active_pane().downgrade(), window, cx)
20575                .detach_and_log_err(cx);
20576
20577            second_item_id
20578        })
20579        .unwrap();
20580    cx.executor().run_until_parked();
20581    workspace
20582        .update(cx, |workspace, _, cx| {
20583            let active_item = workspace
20584                .active_item(cx)
20585                .expect("should have an active item after navigating back from the 2nd buffer");
20586            assert_eq!(
20587                active_item.item_id(),
20588                multibuffer_item_id,
20589                "Should navigate back from the 2nd buffer to the multi buffer"
20590            );
20591            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
20592        })
20593        .unwrap();
20594
20595    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20596        editor.change_selections(
20597            SelectionEffects::scroll(Autoscroll::Next),
20598            window,
20599            cx,
20600            |s| s.select_ranges(Some(MultiBufferOffset(70)..MultiBufferOffset(70))),
20601        );
20602        editor.open_excerpts(&OpenExcerpts, window, cx);
20603    });
20604    cx.executor().run_until_parked();
20605    workspace
20606        .update(cx, |workspace, window, cx| {
20607            let active_item = workspace
20608                .active_item(cx)
20609                .expect("should have an active item after navigating into the 3rd buffer");
20610            let third_item_id = active_item.item_id();
20611            assert_ne!(
20612                third_item_id, multibuffer_item_id,
20613                "Should navigate into the 3rd buffer and activate it"
20614            );
20615            assert_ne!(third_item_id, first_item_id);
20616            assert_ne!(third_item_id, second_item_id);
20617            assert_eq!(
20618                active_item.buffer_kind(cx),
20619                ItemBufferKind::Singleton,
20620                "New active item should be a singleton buffer"
20621            );
20622            assert_eq!(
20623                active_item
20624                    .act_as::<Editor>(cx)
20625                    .expect("should have navigated into an editor")
20626                    .read(cx)
20627                    .text(cx),
20628                sample_text_3
20629            );
20630
20631            workspace
20632                .go_back(workspace.active_pane().downgrade(), window, cx)
20633                .detach_and_log_err(cx);
20634        })
20635        .unwrap();
20636    cx.executor().run_until_parked();
20637    workspace
20638        .update(cx, |workspace, _, cx| {
20639            let active_item = workspace
20640                .active_item(cx)
20641                .expect("should have an active item after navigating back from the 3rd buffer");
20642            assert_eq!(
20643                active_item.item_id(),
20644                multibuffer_item_id,
20645                "Should navigate back from the 3rd buffer to the multi buffer"
20646            );
20647            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
20648        })
20649        .unwrap();
20650}
20651
20652#[gpui::test]
20653async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
20654    init_test(cx, |_| {});
20655
20656    let mut cx = EditorTestContext::new(cx).await;
20657
20658    let diff_base = r#"
20659        use some::mod;
20660
20661        const A: u32 = 42;
20662
20663        fn main() {
20664            println!("hello");
20665
20666            println!("world");
20667        }
20668        "#
20669    .unindent();
20670
20671    cx.set_state(
20672        &r#"
20673        use some::modified;
20674
20675        ˇ
20676        fn main() {
20677            println!("hello there");
20678
20679            println!("around the");
20680            println!("world");
20681        }
20682        "#
20683        .unindent(),
20684    );
20685
20686    cx.set_head_text(&diff_base);
20687    executor.run_until_parked();
20688
20689    cx.update_editor(|editor, window, cx| {
20690        editor.go_to_next_hunk(&GoToHunk, window, cx);
20691        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
20692    });
20693    executor.run_until_parked();
20694    cx.assert_state_with_diff(
20695        r#"
20696          use some::modified;
20697
20698
20699          fn main() {
20700        -     println!("hello");
20701        + ˇ    println!("hello there");
20702
20703              println!("around the");
20704              println!("world");
20705          }
20706        "#
20707        .unindent(),
20708    );
20709
20710    cx.update_editor(|editor, window, cx| {
20711        for _ in 0..2 {
20712            editor.go_to_next_hunk(&GoToHunk, window, cx);
20713            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
20714        }
20715    });
20716    executor.run_until_parked();
20717    cx.assert_state_with_diff(
20718        r#"
20719        - use some::mod;
20720        + ˇuse some::modified;
20721
20722
20723          fn main() {
20724        -     println!("hello");
20725        +     println!("hello there");
20726
20727        +     println!("around the");
20728              println!("world");
20729          }
20730        "#
20731        .unindent(),
20732    );
20733
20734    cx.update_editor(|editor, window, cx| {
20735        editor.go_to_next_hunk(&GoToHunk, window, cx);
20736        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
20737    });
20738    executor.run_until_parked();
20739    cx.assert_state_with_diff(
20740        r#"
20741        - use some::mod;
20742        + use some::modified;
20743
20744        - const A: u32 = 42;
20745          ˇ
20746          fn main() {
20747        -     println!("hello");
20748        +     println!("hello there");
20749
20750        +     println!("around the");
20751              println!("world");
20752          }
20753        "#
20754        .unindent(),
20755    );
20756
20757    cx.update_editor(|editor, window, cx| {
20758        editor.cancel(&Cancel, window, cx);
20759    });
20760
20761    cx.assert_state_with_diff(
20762        r#"
20763          use some::modified;
20764
20765          ˇ
20766          fn main() {
20767              println!("hello there");
20768
20769              println!("around the");
20770              println!("world");
20771          }
20772        "#
20773        .unindent(),
20774    );
20775}
20776
20777#[gpui::test]
20778async fn test_diff_base_change_with_expanded_diff_hunks(
20779    executor: BackgroundExecutor,
20780    cx: &mut TestAppContext,
20781) {
20782    init_test(cx, |_| {});
20783
20784    let mut cx = EditorTestContext::new(cx).await;
20785
20786    let diff_base = r#"
20787        use some::mod1;
20788        use some::mod2;
20789
20790        const A: u32 = 42;
20791        const B: u32 = 42;
20792        const C: u32 = 42;
20793
20794        fn main() {
20795            println!("hello");
20796
20797            println!("world");
20798        }
20799        "#
20800    .unindent();
20801
20802    cx.set_state(
20803        &r#"
20804        use some::mod2;
20805
20806        const A: u32 = 42;
20807        const C: u32 = 42;
20808
20809        fn main(ˇ) {
20810            //println!("hello");
20811
20812            println!("world");
20813            //
20814            //
20815        }
20816        "#
20817        .unindent(),
20818    );
20819
20820    cx.set_head_text(&diff_base);
20821    executor.run_until_parked();
20822
20823    cx.update_editor(|editor, window, cx| {
20824        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20825    });
20826    executor.run_until_parked();
20827    cx.assert_state_with_diff(
20828        r#"
20829        - use some::mod1;
20830          use some::mod2;
20831
20832          const A: u32 = 42;
20833        - const B: u32 = 42;
20834          const C: u32 = 42;
20835
20836          fn main(ˇ) {
20837        -     println!("hello");
20838        +     //println!("hello");
20839
20840              println!("world");
20841        +     //
20842        +     //
20843          }
20844        "#
20845        .unindent(),
20846    );
20847
20848    cx.set_head_text("new diff base!");
20849    executor.run_until_parked();
20850    cx.assert_state_with_diff(
20851        r#"
20852        - new diff base!
20853        + use some::mod2;
20854        +
20855        + const A: u32 = 42;
20856        + const C: u32 = 42;
20857        +
20858        + fn main(ˇ) {
20859        +     //println!("hello");
20860        +
20861        +     println!("world");
20862        +     //
20863        +     //
20864        + }
20865        "#
20866        .unindent(),
20867    );
20868}
20869
20870#[gpui::test]
20871async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
20872    init_test(cx, |_| {});
20873
20874    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
20875    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
20876    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
20877    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
20878    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
20879    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
20880
20881    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
20882    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
20883    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
20884
20885    let multi_buffer = cx.new(|cx| {
20886        let mut multibuffer = MultiBuffer::new(ReadWrite);
20887        multibuffer.push_excerpts(
20888            buffer_1.clone(),
20889            [
20890                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20891                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20892                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
20893            ],
20894            cx,
20895        );
20896        multibuffer.push_excerpts(
20897            buffer_2.clone(),
20898            [
20899                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20900                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20901                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
20902            ],
20903            cx,
20904        );
20905        multibuffer.push_excerpts(
20906            buffer_3.clone(),
20907            [
20908                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20909                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20910                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
20911            ],
20912            cx,
20913        );
20914        multibuffer
20915    });
20916
20917    let editor =
20918        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
20919    editor
20920        .update(cx, |editor, _window, cx| {
20921            for (buffer, diff_base) in [
20922                (buffer_1.clone(), file_1_old),
20923                (buffer_2.clone(), file_2_old),
20924                (buffer_3.clone(), file_3_old),
20925            ] {
20926                let diff = cx.new(|cx| {
20927                    BufferDiff::new_with_base_text(diff_base, &buffer.read(cx).text_snapshot(), cx)
20928                });
20929                editor
20930                    .buffer
20931                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
20932            }
20933        })
20934        .unwrap();
20935
20936    let mut cx = EditorTestContext::for_editor(editor, cx).await;
20937    cx.run_until_parked();
20938
20939    cx.assert_editor_state(
20940        &"
20941            ˇaaa
20942            ccc
20943            ddd
20944
20945            ggg
20946            hhh
20947
20948
20949            lll
20950            mmm
20951            NNN
20952
20953            qqq
20954            rrr
20955
20956            uuu
20957            111
20958            222
20959            333
20960
20961            666
20962            777
20963
20964            000
20965            !!!"
20966        .unindent(),
20967    );
20968
20969    cx.update_editor(|editor, window, cx| {
20970        editor.select_all(&SelectAll, window, cx);
20971        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
20972    });
20973    cx.executor().run_until_parked();
20974
20975    cx.assert_state_with_diff(
20976        "
20977            «aaa
20978          - bbb
20979            ccc
20980            ddd
20981
20982            ggg
20983            hhh
20984
20985
20986            lll
20987            mmm
20988          - nnn
20989          + NNN
20990
20991            qqq
20992            rrr
20993
20994            uuu
20995            111
20996            222
20997            333
20998
20999          + 666
21000            777
21001
21002            000
21003            !!!ˇ»"
21004            .unindent(),
21005    );
21006}
21007
21008#[gpui::test]
21009async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
21010    init_test(cx, |_| {});
21011
21012    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
21013    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
21014
21015    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
21016    let multi_buffer = cx.new(|cx| {
21017        let mut multibuffer = MultiBuffer::new(ReadWrite);
21018        multibuffer.push_excerpts(
21019            buffer.clone(),
21020            [
21021                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
21022                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
21023                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
21024            ],
21025            cx,
21026        );
21027        multibuffer
21028    });
21029
21030    let editor =
21031        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
21032    editor
21033        .update(cx, |editor, _window, cx| {
21034            let diff = cx.new(|cx| {
21035                BufferDiff::new_with_base_text(base, &buffer.read(cx).text_snapshot(), cx)
21036            });
21037            editor
21038                .buffer
21039                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
21040        })
21041        .unwrap();
21042
21043    let mut cx = EditorTestContext::for_editor(editor, cx).await;
21044    cx.run_until_parked();
21045
21046    cx.update_editor(|editor, window, cx| {
21047        editor.expand_all_diff_hunks(&Default::default(), window, cx)
21048    });
21049    cx.executor().run_until_parked();
21050
21051    // When the start of a hunk coincides with the start of its excerpt,
21052    // the hunk is expanded. When the start of a hunk is earlier than
21053    // the start of its excerpt, the hunk is not expanded.
21054    cx.assert_state_with_diff(
21055        "
21056            ˇaaa
21057          - bbb
21058          + BBB
21059
21060          - ddd
21061          - eee
21062          + DDD
21063          + EEE
21064            fff
21065
21066            iii
21067        "
21068        .unindent(),
21069    );
21070}
21071
21072#[gpui::test]
21073async fn test_edits_around_expanded_insertion_hunks(
21074    executor: BackgroundExecutor,
21075    cx: &mut TestAppContext,
21076) {
21077    init_test(cx, |_| {});
21078
21079    let mut cx = EditorTestContext::new(cx).await;
21080
21081    let diff_base = r#"
21082        use some::mod1;
21083        use some::mod2;
21084
21085        const A: u32 = 42;
21086
21087        fn main() {
21088            println!("hello");
21089
21090            println!("world");
21091        }
21092        "#
21093    .unindent();
21094    executor.run_until_parked();
21095    cx.set_state(
21096        &r#"
21097        use some::mod1;
21098        use some::mod2;
21099
21100        const A: u32 = 42;
21101        const B: u32 = 42;
21102        const C: u32 = 42;
21103        ˇ
21104
21105        fn main() {
21106            println!("hello");
21107
21108            println!("world");
21109        }
21110        "#
21111        .unindent(),
21112    );
21113
21114    cx.set_head_text(&diff_base);
21115    executor.run_until_parked();
21116
21117    cx.update_editor(|editor, window, cx| {
21118        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21119    });
21120    executor.run_until_parked();
21121
21122    cx.assert_state_with_diff(
21123        r#"
21124        use some::mod1;
21125        use some::mod2;
21126
21127        const A: u32 = 42;
21128      + const B: u32 = 42;
21129      + const C: u32 = 42;
21130      + ˇ
21131
21132        fn main() {
21133            println!("hello");
21134
21135            println!("world");
21136        }
21137      "#
21138        .unindent(),
21139    );
21140
21141    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
21142    executor.run_until_parked();
21143
21144    cx.assert_state_with_diff(
21145        r#"
21146        use some::mod1;
21147        use some::mod2;
21148
21149        const A: u32 = 42;
21150      + const B: u32 = 42;
21151      + const C: u32 = 42;
21152      + const D: u32 = 42;
21153      + ˇ
21154
21155        fn main() {
21156            println!("hello");
21157
21158            println!("world");
21159        }
21160      "#
21161        .unindent(),
21162    );
21163
21164    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
21165    executor.run_until_parked();
21166
21167    cx.assert_state_with_diff(
21168        r#"
21169        use some::mod1;
21170        use some::mod2;
21171
21172        const A: u32 = 42;
21173      + const B: u32 = 42;
21174      + const C: u32 = 42;
21175      + const D: u32 = 42;
21176      + const E: u32 = 42;
21177      + ˇ
21178
21179        fn main() {
21180            println!("hello");
21181
21182            println!("world");
21183        }
21184      "#
21185        .unindent(),
21186    );
21187
21188    cx.update_editor(|editor, window, cx| {
21189        editor.delete_line(&DeleteLine, window, cx);
21190    });
21191    executor.run_until_parked();
21192
21193    cx.assert_state_with_diff(
21194        r#"
21195        use some::mod1;
21196        use some::mod2;
21197
21198        const A: u32 = 42;
21199      + const B: u32 = 42;
21200      + const C: u32 = 42;
21201      + const D: u32 = 42;
21202      + const E: u32 = 42;
21203        ˇ
21204        fn main() {
21205            println!("hello");
21206
21207            println!("world");
21208        }
21209      "#
21210        .unindent(),
21211    );
21212
21213    cx.update_editor(|editor, window, cx| {
21214        editor.move_up(&MoveUp, window, cx);
21215        editor.delete_line(&DeleteLine, window, cx);
21216        editor.move_up(&MoveUp, window, cx);
21217        editor.delete_line(&DeleteLine, window, cx);
21218        editor.move_up(&MoveUp, window, cx);
21219        editor.delete_line(&DeleteLine, window, cx);
21220    });
21221    executor.run_until_parked();
21222    cx.assert_state_with_diff(
21223        r#"
21224        use some::mod1;
21225        use some::mod2;
21226
21227        const A: u32 = 42;
21228      + const B: u32 = 42;
21229        ˇ
21230        fn main() {
21231            println!("hello");
21232
21233            println!("world");
21234        }
21235      "#
21236        .unindent(),
21237    );
21238
21239    cx.update_editor(|editor, window, cx| {
21240        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
21241        editor.delete_line(&DeleteLine, window, cx);
21242    });
21243    executor.run_until_parked();
21244    cx.assert_state_with_diff(
21245        r#"
21246        ˇ
21247        fn main() {
21248            println!("hello");
21249
21250            println!("world");
21251        }
21252      "#
21253        .unindent(),
21254    );
21255}
21256
21257#[gpui::test]
21258async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
21259    init_test(cx, |_| {});
21260
21261    let mut cx = EditorTestContext::new(cx).await;
21262    cx.set_head_text(indoc! { "
21263        one
21264        two
21265        three
21266        four
21267        five
21268        "
21269    });
21270    cx.set_state(indoc! { "
21271        one
21272        ˇthree
21273        five
21274    "});
21275    cx.run_until_parked();
21276    cx.update_editor(|editor, window, cx| {
21277        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
21278    });
21279    cx.assert_state_with_diff(
21280        indoc! { "
21281        one
21282      - two
21283        ˇthree
21284      - four
21285        five
21286    "}
21287        .to_string(),
21288    );
21289    cx.update_editor(|editor, window, cx| {
21290        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
21291    });
21292
21293    cx.assert_state_with_diff(
21294        indoc! { "
21295        one
21296        ˇthree
21297        five
21298    "}
21299        .to_string(),
21300    );
21301
21302    cx.update_editor(|editor, window, cx| {
21303        editor.move_up(&MoveUp, window, cx);
21304        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
21305    });
21306    cx.assert_state_with_diff(
21307        indoc! { "
21308        ˇone
21309      - two
21310        three
21311        five
21312    "}
21313        .to_string(),
21314    );
21315
21316    cx.update_editor(|editor, window, cx| {
21317        editor.move_down(&MoveDown, window, cx);
21318        editor.move_down(&MoveDown, window, cx);
21319        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
21320    });
21321    cx.assert_state_with_diff(
21322        indoc! { "
21323        one
21324      - two
21325        ˇthree
21326      - four
21327        five
21328    "}
21329        .to_string(),
21330    );
21331
21332    cx.set_state(indoc! { "
21333        one
21334        ˇTWO
21335        three
21336        four
21337        five
21338    "});
21339    cx.run_until_parked();
21340    cx.update_editor(|editor, window, cx| {
21341        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
21342    });
21343
21344    cx.assert_state_with_diff(
21345        indoc! { "
21346            one
21347          - two
21348          + ˇTWO
21349            three
21350            four
21351            five
21352        "}
21353        .to_string(),
21354    );
21355    cx.update_editor(|editor, window, cx| {
21356        editor.move_up(&Default::default(), window, cx);
21357        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
21358    });
21359    cx.assert_state_with_diff(
21360        indoc! { "
21361            one
21362            ˇTWO
21363            three
21364            four
21365            five
21366        "}
21367        .to_string(),
21368    );
21369}
21370
21371#[gpui::test]
21372async fn test_toggling_adjacent_diff_hunks_2(
21373    executor: BackgroundExecutor,
21374    cx: &mut TestAppContext,
21375) {
21376    init_test(cx, |_| {});
21377
21378    let mut cx = EditorTestContext::new(cx).await;
21379
21380    let diff_base = r#"
21381        lineA
21382        lineB
21383        lineC
21384        lineD
21385        "#
21386    .unindent();
21387
21388    cx.set_state(
21389        &r#"
21390        ˇlineA1
21391        lineB
21392        lineD
21393        "#
21394        .unindent(),
21395    );
21396    cx.set_head_text(&diff_base);
21397    executor.run_until_parked();
21398
21399    cx.update_editor(|editor, window, cx| {
21400        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
21401    });
21402    executor.run_until_parked();
21403    cx.assert_state_with_diff(
21404        r#"
21405        - lineA
21406        + ˇlineA1
21407          lineB
21408          lineD
21409        "#
21410        .unindent(),
21411    );
21412
21413    cx.update_editor(|editor, window, cx| {
21414        editor.move_down(&MoveDown, window, cx);
21415        editor.move_right(&MoveRight, window, cx);
21416        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
21417    });
21418    executor.run_until_parked();
21419    cx.assert_state_with_diff(
21420        r#"
21421        - lineA
21422        + lineA1
21423          lˇineB
21424        - lineC
21425          lineD
21426        "#
21427        .unindent(),
21428    );
21429}
21430
21431#[gpui::test]
21432async fn test_edits_around_expanded_deletion_hunks(
21433    executor: BackgroundExecutor,
21434    cx: &mut TestAppContext,
21435) {
21436    init_test(cx, |_| {});
21437
21438    let mut cx = EditorTestContext::new(cx).await;
21439
21440    let diff_base = r#"
21441        use some::mod1;
21442        use some::mod2;
21443
21444        const A: u32 = 42;
21445        const B: u32 = 42;
21446        const C: u32 = 42;
21447
21448
21449        fn main() {
21450            println!("hello");
21451
21452            println!("world");
21453        }
21454    "#
21455    .unindent();
21456    executor.run_until_parked();
21457    cx.set_state(
21458        &r#"
21459        use some::mod1;
21460        use some::mod2;
21461
21462        ˇconst B: u32 = 42;
21463        const C: u32 = 42;
21464
21465
21466        fn main() {
21467            println!("hello");
21468
21469            println!("world");
21470        }
21471        "#
21472        .unindent(),
21473    );
21474
21475    cx.set_head_text(&diff_base);
21476    executor.run_until_parked();
21477
21478    cx.update_editor(|editor, window, cx| {
21479        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21480    });
21481    executor.run_until_parked();
21482
21483    cx.assert_state_with_diff(
21484        r#"
21485        use some::mod1;
21486        use some::mod2;
21487
21488      - const A: u32 = 42;
21489        ˇconst B: u32 = 42;
21490        const C: u32 = 42;
21491
21492
21493        fn main() {
21494            println!("hello");
21495
21496            println!("world");
21497        }
21498      "#
21499        .unindent(),
21500    );
21501
21502    cx.update_editor(|editor, window, cx| {
21503        editor.delete_line(&DeleteLine, window, cx);
21504    });
21505    executor.run_until_parked();
21506    cx.assert_state_with_diff(
21507        r#"
21508        use some::mod1;
21509        use some::mod2;
21510
21511      - const A: u32 = 42;
21512      - const B: u32 = 42;
21513        ˇconst C: u32 = 42;
21514
21515
21516        fn main() {
21517            println!("hello");
21518
21519            println!("world");
21520        }
21521      "#
21522        .unindent(),
21523    );
21524
21525    cx.update_editor(|editor, window, cx| {
21526        editor.delete_line(&DeleteLine, window, cx);
21527    });
21528    executor.run_until_parked();
21529    cx.assert_state_with_diff(
21530        r#"
21531        use some::mod1;
21532        use some::mod2;
21533
21534      - const A: u32 = 42;
21535      - const B: u32 = 42;
21536      - const C: u32 = 42;
21537        ˇ
21538
21539        fn main() {
21540            println!("hello");
21541
21542            println!("world");
21543        }
21544      "#
21545        .unindent(),
21546    );
21547
21548    cx.update_editor(|editor, window, cx| {
21549        editor.handle_input("replacement", window, cx);
21550    });
21551    executor.run_until_parked();
21552    cx.assert_state_with_diff(
21553        r#"
21554        use some::mod1;
21555        use some::mod2;
21556
21557      - const A: u32 = 42;
21558      - const B: u32 = 42;
21559      - const C: u32 = 42;
21560      -
21561      + replacementˇ
21562
21563        fn main() {
21564            println!("hello");
21565
21566            println!("world");
21567        }
21568      "#
21569        .unindent(),
21570    );
21571}
21572
21573#[gpui::test]
21574async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
21575    init_test(cx, |_| {});
21576
21577    let mut cx = EditorTestContext::new(cx).await;
21578
21579    let base_text = r#"
21580        one
21581        two
21582        three
21583        four
21584        five
21585    "#
21586    .unindent();
21587    executor.run_until_parked();
21588    cx.set_state(
21589        &r#"
21590        one
21591        two
21592        fˇour
21593        five
21594        "#
21595        .unindent(),
21596    );
21597
21598    cx.set_head_text(&base_text);
21599    executor.run_until_parked();
21600
21601    cx.update_editor(|editor, window, cx| {
21602        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21603    });
21604    executor.run_until_parked();
21605
21606    cx.assert_state_with_diff(
21607        r#"
21608          one
21609          two
21610        - three
21611          fˇour
21612          five
21613        "#
21614        .unindent(),
21615    );
21616
21617    cx.update_editor(|editor, window, cx| {
21618        editor.backspace(&Backspace, window, cx);
21619        editor.backspace(&Backspace, window, cx);
21620    });
21621    executor.run_until_parked();
21622    cx.assert_state_with_diff(
21623        r#"
21624          one
21625          two
21626        - threeˇ
21627        - four
21628        + our
21629          five
21630        "#
21631        .unindent(),
21632    );
21633}
21634
21635#[gpui::test]
21636async fn test_edit_after_expanded_modification_hunk(
21637    executor: BackgroundExecutor,
21638    cx: &mut TestAppContext,
21639) {
21640    init_test(cx, |_| {});
21641
21642    let mut cx = EditorTestContext::new(cx).await;
21643
21644    let diff_base = r#"
21645        use some::mod1;
21646        use some::mod2;
21647
21648        const A: u32 = 42;
21649        const B: u32 = 42;
21650        const C: u32 = 42;
21651        const D: u32 = 42;
21652
21653
21654        fn main() {
21655            println!("hello");
21656
21657            println!("world");
21658        }"#
21659    .unindent();
21660
21661    cx.set_state(
21662        &r#"
21663        use some::mod1;
21664        use some::mod2;
21665
21666        const A: u32 = 42;
21667        const B: u32 = 42;
21668        const C: u32 = 43ˇ
21669        const D: u32 = 42;
21670
21671
21672        fn main() {
21673            println!("hello");
21674
21675            println!("world");
21676        }"#
21677        .unindent(),
21678    );
21679
21680    cx.set_head_text(&diff_base);
21681    executor.run_until_parked();
21682    cx.update_editor(|editor, window, cx| {
21683        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21684    });
21685    executor.run_until_parked();
21686
21687    cx.assert_state_with_diff(
21688        r#"
21689        use some::mod1;
21690        use some::mod2;
21691
21692        const A: u32 = 42;
21693        const B: u32 = 42;
21694      - const C: u32 = 42;
21695      + const C: u32 = 43ˇ
21696        const D: u32 = 42;
21697
21698
21699        fn main() {
21700            println!("hello");
21701
21702            println!("world");
21703        }"#
21704        .unindent(),
21705    );
21706
21707    cx.update_editor(|editor, window, cx| {
21708        editor.handle_input("\nnew_line\n", window, cx);
21709    });
21710    executor.run_until_parked();
21711
21712    cx.assert_state_with_diff(
21713        r#"
21714        use some::mod1;
21715        use some::mod2;
21716
21717        const A: u32 = 42;
21718        const B: u32 = 42;
21719      - const C: u32 = 42;
21720      + const C: u32 = 43
21721      + new_line
21722      + ˇ
21723        const D: u32 = 42;
21724
21725
21726        fn main() {
21727            println!("hello");
21728
21729            println!("world");
21730        }"#
21731        .unindent(),
21732    );
21733}
21734
21735#[gpui::test]
21736async fn test_stage_and_unstage_added_file_hunk(
21737    executor: BackgroundExecutor,
21738    cx: &mut TestAppContext,
21739) {
21740    init_test(cx, |_| {});
21741
21742    let mut cx = EditorTestContext::new(cx).await;
21743    cx.update_editor(|editor, _, cx| {
21744        editor.set_expand_all_diff_hunks(cx);
21745    });
21746
21747    let working_copy = r#"
21748            ˇfn main() {
21749                println!("hello, world!");
21750            }
21751        "#
21752    .unindent();
21753
21754    cx.set_state(&working_copy);
21755    executor.run_until_parked();
21756
21757    cx.assert_state_with_diff(
21758        r#"
21759            + ˇfn main() {
21760            +     println!("hello, world!");
21761            + }
21762        "#
21763        .unindent(),
21764    );
21765    cx.assert_index_text(None);
21766
21767    cx.update_editor(|editor, window, cx| {
21768        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21769    });
21770    executor.run_until_parked();
21771    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
21772    cx.assert_state_with_diff(
21773        r#"
21774            + ˇfn main() {
21775            +     println!("hello, world!");
21776            + }
21777        "#
21778        .unindent(),
21779    );
21780
21781    cx.update_editor(|editor, window, cx| {
21782        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21783    });
21784    executor.run_until_parked();
21785    cx.assert_index_text(None);
21786}
21787
21788async fn setup_indent_guides_editor(
21789    text: &str,
21790    cx: &mut TestAppContext,
21791) -> (BufferId, EditorTestContext) {
21792    init_test(cx, |_| {});
21793
21794    let mut cx = EditorTestContext::new(cx).await;
21795
21796    let buffer_id = cx.update_editor(|editor, window, cx| {
21797        editor.set_text(text, window, cx);
21798        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
21799
21800        buffer_ids[0]
21801    });
21802
21803    (buffer_id, cx)
21804}
21805
21806fn assert_indent_guides(
21807    range: Range<u32>,
21808    expected: Vec<IndentGuide>,
21809    active_indices: Option<Vec<usize>>,
21810    cx: &mut EditorTestContext,
21811) {
21812    let indent_guides = cx.update_editor(|editor, window, cx| {
21813        let snapshot = editor.snapshot(window, cx).display_snapshot;
21814        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
21815            editor,
21816            MultiBufferRow(range.start)..MultiBufferRow(range.end),
21817            true,
21818            &snapshot,
21819            cx,
21820        );
21821
21822        indent_guides.sort_by(|a, b| {
21823            a.depth.cmp(&b.depth).then(
21824                a.start_row
21825                    .cmp(&b.start_row)
21826                    .then(a.end_row.cmp(&b.end_row)),
21827            )
21828        });
21829        indent_guides
21830    });
21831
21832    if let Some(expected) = active_indices {
21833        let active_indices = cx.update_editor(|editor, window, cx| {
21834            let snapshot = editor.snapshot(window, cx).display_snapshot;
21835            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
21836        });
21837
21838        assert_eq!(
21839            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
21840            expected,
21841            "Active indent guide indices do not match"
21842        );
21843    }
21844
21845    assert_eq!(indent_guides, expected, "Indent guides do not match");
21846}
21847
21848fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
21849    IndentGuide {
21850        buffer_id,
21851        start_row: MultiBufferRow(start_row),
21852        end_row: MultiBufferRow(end_row),
21853        depth,
21854        tab_size: 4,
21855        settings: IndentGuideSettings {
21856            enabled: true,
21857            line_width: 1,
21858            active_line_width: 1,
21859            coloring: IndentGuideColoring::default(),
21860            background_coloring: IndentGuideBackgroundColoring::default(),
21861        },
21862    }
21863}
21864
21865#[gpui::test]
21866async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
21867    let (buffer_id, mut cx) = setup_indent_guides_editor(
21868        &"
21869        fn main() {
21870            let a = 1;
21871        }"
21872        .unindent(),
21873        cx,
21874    )
21875    .await;
21876
21877    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
21878}
21879
21880#[gpui::test]
21881async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
21882    let (buffer_id, mut cx) = setup_indent_guides_editor(
21883        &"
21884        fn main() {
21885            let a = 1;
21886            let b = 2;
21887        }"
21888        .unindent(),
21889        cx,
21890    )
21891    .await;
21892
21893    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
21894}
21895
21896#[gpui::test]
21897async fn test_indent_guide_nested(cx: &mut TestAppContext) {
21898    let (buffer_id, mut cx) = setup_indent_guides_editor(
21899        &"
21900        fn main() {
21901            let a = 1;
21902            if a == 3 {
21903                let b = 2;
21904            } else {
21905                let c = 3;
21906            }
21907        }"
21908        .unindent(),
21909        cx,
21910    )
21911    .await;
21912
21913    assert_indent_guides(
21914        0..8,
21915        vec![
21916            indent_guide(buffer_id, 1, 6, 0),
21917            indent_guide(buffer_id, 3, 3, 1),
21918            indent_guide(buffer_id, 5, 5, 1),
21919        ],
21920        None,
21921        &mut cx,
21922    );
21923}
21924
21925#[gpui::test]
21926async fn test_indent_guide_tab(cx: &mut TestAppContext) {
21927    let (buffer_id, mut cx) = setup_indent_guides_editor(
21928        &"
21929        fn main() {
21930            let a = 1;
21931                let b = 2;
21932            let c = 3;
21933        }"
21934        .unindent(),
21935        cx,
21936    )
21937    .await;
21938
21939    assert_indent_guides(
21940        0..5,
21941        vec![
21942            indent_guide(buffer_id, 1, 3, 0),
21943            indent_guide(buffer_id, 2, 2, 1),
21944        ],
21945        None,
21946        &mut cx,
21947    );
21948}
21949
21950#[gpui::test]
21951async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
21952    let (buffer_id, mut cx) = setup_indent_guides_editor(
21953        &"
21954        fn main() {
21955            let a = 1;
21956
21957            let c = 3;
21958        }"
21959        .unindent(),
21960        cx,
21961    )
21962    .await;
21963
21964    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
21965}
21966
21967#[gpui::test]
21968async fn test_indent_guide_complex(cx: &mut TestAppContext) {
21969    let (buffer_id, mut cx) = setup_indent_guides_editor(
21970        &"
21971        fn main() {
21972            let a = 1;
21973
21974            let c = 3;
21975
21976            if a == 3 {
21977                let b = 2;
21978            } else {
21979                let c = 3;
21980            }
21981        }"
21982        .unindent(),
21983        cx,
21984    )
21985    .await;
21986
21987    assert_indent_guides(
21988        0..11,
21989        vec![
21990            indent_guide(buffer_id, 1, 9, 0),
21991            indent_guide(buffer_id, 6, 6, 1),
21992            indent_guide(buffer_id, 8, 8, 1),
21993        ],
21994        None,
21995        &mut cx,
21996    );
21997}
21998
21999#[gpui::test]
22000async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
22001    let (buffer_id, mut cx) = setup_indent_guides_editor(
22002        &"
22003        fn main() {
22004            let a = 1;
22005
22006            let c = 3;
22007
22008            if a == 3 {
22009                let b = 2;
22010            } else {
22011                let c = 3;
22012            }
22013        }"
22014        .unindent(),
22015        cx,
22016    )
22017    .await;
22018
22019    assert_indent_guides(
22020        1..11,
22021        vec![
22022            indent_guide(buffer_id, 1, 9, 0),
22023            indent_guide(buffer_id, 6, 6, 1),
22024            indent_guide(buffer_id, 8, 8, 1),
22025        ],
22026        None,
22027        &mut cx,
22028    );
22029}
22030
22031#[gpui::test]
22032async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
22033    let (buffer_id, mut cx) = setup_indent_guides_editor(
22034        &"
22035        fn main() {
22036            let a = 1;
22037
22038            let c = 3;
22039
22040            if a == 3 {
22041                let b = 2;
22042            } else {
22043                let c = 3;
22044            }
22045        }"
22046        .unindent(),
22047        cx,
22048    )
22049    .await;
22050
22051    assert_indent_guides(
22052        1..10,
22053        vec![
22054            indent_guide(buffer_id, 1, 9, 0),
22055            indent_guide(buffer_id, 6, 6, 1),
22056            indent_guide(buffer_id, 8, 8, 1),
22057        ],
22058        None,
22059        &mut cx,
22060    );
22061}
22062
22063#[gpui::test]
22064async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
22065    let (buffer_id, mut cx) = setup_indent_guides_editor(
22066        &"
22067        fn main() {
22068            if a {
22069                b(
22070                    c,
22071                    d,
22072                )
22073            } else {
22074                e(
22075                    f
22076                )
22077            }
22078        }"
22079        .unindent(),
22080        cx,
22081    )
22082    .await;
22083
22084    assert_indent_guides(
22085        0..11,
22086        vec![
22087            indent_guide(buffer_id, 1, 10, 0),
22088            indent_guide(buffer_id, 2, 5, 1),
22089            indent_guide(buffer_id, 7, 9, 1),
22090            indent_guide(buffer_id, 3, 4, 2),
22091            indent_guide(buffer_id, 8, 8, 2),
22092        ],
22093        None,
22094        &mut cx,
22095    );
22096
22097    cx.update_editor(|editor, window, cx| {
22098        editor.fold_at(MultiBufferRow(2), window, cx);
22099        assert_eq!(
22100            editor.display_text(cx),
22101            "
22102            fn main() {
22103                if a {
22104                    b(⋯
22105                    )
22106                } else {
22107                    e(
22108                        f
22109                    )
22110                }
22111            }"
22112            .unindent()
22113        );
22114    });
22115
22116    assert_indent_guides(
22117        0..11,
22118        vec![
22119            indent_guide(buffer_id, 1, 10, 0),
22120            indent_guide(buffer_id, 2, 5, 1),
22121            indent_guide(buffer_id, 7, 9, 1),
22122            indent_guide(buffer_id, 8, 8, 2),
22123        ],
22124        None,
22125        &mut cx,
22126    );
22127}
22128
22129#[gpui::test]
22130async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
22131    let (buffer_id, mut cx) = setup_indent_guides_editor(
22132        &"
22133        block1
22134            block2
22135                block3
22136                    block4
22137            block2
22138        block1
22139        block1"
22140            .unindent(),
22141        cx,
22142    )
22143    .await;
22144
22145    assert_indent_guides(
22146        1..10,
22147        vec![
22148            indent_guide(buffer_id, 1, 4, 0),
22149            indent_guide(buffer_id, 2, 3, 1),
22150            indent_guide(buffer_id, 3, 3, 2),
22151        ],
22152        None,
22153        &mut cx,
22154    );
22155}
22156
22157#[gpui::test]
22158async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
22159    let (buffer_id, mut cx) = setup_indent_guides_editor(
22160        &"
22161        block1
22162            block2
22163                block3
22164
22165        block1
22166        block1"
22167            .unindent(),
22168        cx,
22169    )
22170    .await;
22171
22172    assert_indent_guides(
22173        0..6,
22174        vec![
22175            indent_guide(buffer_id, 1, 2, 0),
22176            indent_guide(buffer_id, 2, 2, 1),
22177        ],
22178        None,
22179        &mut cx,
22180    );
22181}
22182
22183#[gpui::test]
22184async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
22185    let (buffer_id, mut cx) = setup_indent_guides_editor(
22186        &"
22187        function component() {
22188        \treturn (
22189        \t\t\t
22190        \t\t<div>
22191        \t\t\t<abc></abc>
22192        \t\t</div>
22193        \t)
22194        }"
22195        .unindent(),
22196        cx,
22197    )
22198    .await;
22199
22200    assert_indent_guides(
22201        0..8,
22202        vec![
22203            indent_guide(buffer_id, 1, 6, 0),
22204            indent_guide(buffer_id, 2, 5, 1),
22205            indent_guide(buffer_id, 4, 4, 2),
22206        ],
22207        None,
22208        &mut cx,
22209    );
22210}
22211
22212#[gpui::test]
22213async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
22214    let (buffer_id, mut cx) = setup_indent_guides_editor(
22215        &"
22216        function component() {
22217        \treturn (
22218        \t
22219        \t\t<div>
22220        \t\t\t<abc></abc>
22221        \t\t</div>
22222        \t)
22223        }"
22224        .unindent(),
22225        cx,
22226    )
22227    .await;
22228
22229    assert_indent_guides(
22230        0..8,
22231        vec![
22232            indent_guide(buffer_id, 1, 6, 0),
22233            indent_guide(buffer_id, 2, 5, 1),
22234            indent_guide(buffer_id, 4, 4, 2),
22235        ],
22236        None,
22237        &mut cx,
22238    );
22239}
22240
22241#[gpui::test]
22242async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
22243    let (buffer_id, mut cx) = setup_indent_guides_editor(
22244        &"
22245        block1
22246
22247
22248
22249            block2
22250        "
22251        .unindent(),
22252        cx,
22253    )
22254    .await;
22255
22256    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
22257}
22258
22259#[gpui::test]
22260async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
22261    let (buffer_id, mut cx) = setup_indent_guides_editor(
22262        &"
22263        def a:
22264        \tb = 3
22265        \tif True:
22266        \t\tc = 4
22267        \t\td = 5
22268        \tprint(b)
22269        "
22270        .unindent(),
22271        cx,
22272    )
22273    .await;
22274
22275    assert_indent_guides(
22276        0..6,
22277        vec![
22278            indent_guide(buffer_id, 1, 5, 0),
22279            indent_guide(buffer_id, 3, 4, 1),
22280        ],
22281        None,
22282        &mut cx,
22283    );
22284}
22285
22286#[gpui::test]
22287async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
22288    let (buffer_id, mut cx) = setup_indent_guides_editor(
22289        &"
22290    fn main() {
22291        let a = 1;
22292    }"
22293        .unindent(),
22294        cx,
22295    )
22296    .await;
22297
22298    cx.update_editor(|editor, window, cx| {
22299        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22300            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
22301        });
22302    });
22303
22304    assert_indent_guides(
22305        0..3,
22306        vec![indent_guide(buffer_id, 1, 1, 0)],
22307        Some(vec![0]),
22308        &mut cx,
22309    );
22310}
22311
22312#[gpui::test]
22313async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
22314    let (buffer_id, mut cx) = setup_indent_guides_editor(
22315        &"
22316    fn main() {
22317        if 1 == 2 {
22318            let a = 1;
22319        }
22320    }"
22321        .unindent(),
22322        cx,
22323    )
22324    .await;
22325
22326    cx.update_editor(|editor, window, cx| {
22327        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22328            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
22329        });
22330    });
22331
22332    assert_indent_guides(
22333        0..4,
22334        vec![
22335            indent_guide(buffer_id, 1, 3, 0),
22336            indent_guide(buffer_id, 2, 2, 1),
22337        ],
22338        Some(vec![1]),
22339        &mut cx,
22340    );
22341
22342    cx.update_editor(|editor, window, cx| {
22343        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22344            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
22345        });
22346    });
22347
22348    assert_indent_guides(
22349        0..4,
22350        vec![
22351            indent_guide(buffer_id, 1, 3, 0),
22352            indent_guide(buffer_id, 2, 2, 1),
22353        ],
22354        Some(vec![1]),
22355        &mut cx,
22356    );
22357
22358    cx.update_editor(|editor, window, cx| {
22359        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22360            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
22361        });
22362    });
22363
22364    assert_indent_guides(
22365        0..4,
22366        vec![
22367            indent_guide(buffer_id, 1, 3, 0),
22368            indent_guide(buffer_id, 2, 2, 1),
22369        ],
22370        Some(vec![0]),
22371        &mut cx,
22372    );
22373}
22374
22375#[gpui::test]
22376async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
22377    let (buffer_id, mut cx) = setup_indent_guides_editor(
22378        &"
22379    fn main() {
22380        let a = 1;
22381
22382        let b = 2;
22383    }"
22384        .unindent(),
22385        cx,
22386    )
22387    .await;
22388
22389    cx.update_editor(|editor, window, cx| {
22390        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22391            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
22392        });
22393    });
22394
22395    assert_indent_guides(
22396        0..5,
22397        vec![indent_guide(buffer_id, 1, 3, 0)],
22398        Some(vec![0]),
22399        &mut cx,
22400    );
22401}
22402
22403#[gpui::test]
22404async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
22405    let (buffer_id, mut cx) = setup_indent_guides_editor(
22406        &"
22407    def m:
22408        a = 1
22409        pass"
22410            .unindent(),
22411        cx,
22412    )
22413    .await;
22414
22415    cx.update_editor(|editor, window, cx| {
22416        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22417            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
22418        });
22419    });
22420
22421    assert_indent_guides(
22422        0..3,
22423        vec![indent_guide(buffer_id, 1, 2, 0)],
22424        Some(vec![0]),
22425        &mut cx,
22426    );
22427}
22428
22429#[gpui::test]
22430async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
22431    init_test(cx, |_| {});
22432    let mut cx = EditorTestContext::new(cx).await;
22433    let text = indoc! {
22434        "
22435        impl A {
22436            fn b() {
22437                0;
22438                3;
22439                5;
22440                6;
22441                7;
22442            }
22443        }
22444        "
22445    };
22446    let base_text = indoc! {
22447        "
22448        impl A {
22449            fn b() {
22450                0;
22451                1;
22452                2;
22453                3;
22454                4;
22455            }
22456            fn c() {
22457                5;
22458                6;
22459                7;
22460            }
22461        }
22462        "
22463    };
22464
22465    cx.update_editor(|editor, window, cx| {
22466        editor.set_text(text, window, cx);
22467
22468        editor.buffer().update(cx, |multibuffer, cx| {
22469            let buffer = multibuffer.as_singleton().unwrap();
22470            let diff = cx.new(|cx| {
22471                BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx)
22472            });
22473
22474            multibuffer.set_all_diff_hunks_expanded(cx);
22475            multibuffer.add_diff(diff, cx);
22476
22477            buffer.read(cx).remote_id()
22478        })
22479    });
22480    cx.run_until_parked();
22481
22482    cx.assert_state_with_diff(
22483        indoc! { "
22484          impl A {
22485              fn b() {
22486                  0;
22487        -         1;
22488        -         2;
22489                  3;
22490        -         4;
22491        -     }
22492        -     fn c() {
22493                  5;
22494                  6;
22495                  7;
22496              }
22497          }
22498          ˇ"
22499        }
22500        .to_string(),
22501    );
22502
22503    let mut actual_guides = cx.update_editor(|editor, window, cx| {
22504        editor
22505            .snapshot(window, cx)
22506            .buffer_snapshot()
22507            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
22508            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
22509            .collect::<Vec<_>>()
22510    });
22511    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
22512    assert_eq!(
22513        actual_guides,
22514        vec![
22515            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
22516            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
22517            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
22518        ]
22519    );
22520}
22521
22522#[gpui::test]
22523async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
22524    init_test(cx, |_| {});
22525    let mut cx = EditorTestContext::new(cx).await;
22526
22527    let diff_base = r#"
22528        a
22529        b
22530        c
22531        "#
22532    .unindent();
22533
22534    cx.set_state(
22535        &r#"
22536        ˇA
22537        b
22538        C
22539        "#
22540        .unindent(),
22541    );
22542    cx.set_head_text(&diff_base);
22543    cx.update_editor(|editor, window, cx| {
22544        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
22545    });
22546    executor.run_until_parked();
22547
22548    let both_hunks_expanded = r#"
22549        - a
22550        + ˇA
22551          b
22552        - c
22553        + C
22554        "#
22555    .unindent();
22556
22557    cx.assert_state_with_diff(both_hunks_expanded.clone());
22558
22559    let hunk_ranges = cx.update_editor(|editor, window, cx| {
22560        let snapshot = editor.snapshot(window, cx);
22561        let hunks = editor
22562            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
22563            .collect::<Vec<_>>();
22564        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
22565        hunks
22566            .into_iter()
22567            .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
22568            .collect::<Vec<_>>()
22569    });
22570    assert_eq!(hunk_ranges.len(), 2);
22571
22572    cx.update_editor(|editor, _, cx| {
22573        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
22574    });
22575    executor.run_until_parked();
22576
22577    let second_hunk_expanded = r#"
22578          ˇA
22579          b
22580        - c
22581        + C
22582        "#
22583    .unindent();
22584
22585    cx.assert_state_with_diff(second_hunk_expanded);
22586
22587    cx.update_editor(|editor, _, cx| {
22588        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
22589    });
22590    executor.run_until_parked();
22591
22592    cx.assert_state_with_diff(both_hunks_expanded.clone());
22593
22594    cx.update_editor(|editor, _, cx| {
22595        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
22596    });
22597    executor.run_until_parked();
22598
22599    let first_hunk_expanded = r#"
22600        - a
22601        + ˇA
22602          b
22603          C
22604        "#
22605    .unindent();
22606
22607    cx.assert_state_with_diff(first_hunk_expanded);
22608
22609    cx.update_editor(|editor, _, cx| {
22610        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
22611    });
22612    executor.run_until_parked();
22613
22614    cx.assert_state_with_diff(both_hunks_expanded);
22615
22616    cx.set_state(
22617        &r#"
22618        ˇA
22619        b
22620        "#
22621        .unindent(),
22622    );
22623    cx.run_until_parked();
22624
22625    // TODO this cursor position seems bad
22626    cx.assert_state_with_diff(
22627        r#"
22628        - ˇa
22629        + A
22630          b
22631        "#
22632        .unindent(),
22633    );
22634
22635    cx.update_editor(|editor, window, cx| {
22636        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
22637    });
22638
22639    cx.assert_state_with_diff(
22640        r#"
22641            - ˇa
22642            + A
22643              b
22644            - c
22645            "#
22646        .unindent(),
22647    );
22648
22649    let hunk_ranges = cx.update_editor(|editor, window, cx| {
22650        let snapshot = editor.snapshot(window, cx);
22651        let hunks = editor
22652            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
22653            .collect::<Vec<_>>();
22654        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
22655        hunks
22656            .into_iter()
22657            .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
22658            .collect::<Vec<_>>()
22659    });
22660    assert_eq!(hunk_ranges.len(), 2);
22661
22662    cx.update_editor(|editor, _, cx| {
22663        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
22664    });
22665    executor.run_until_parked();
22666
22667    cx.assert_state_with_diff(
22668        r#"
22669        - ˇa
22670        + A
22671          b
22672        "#
22673        .unindent(),
22674    );
22675}
22676
22677#[gpui::test]
22678async fn test_toggle_deletion_hunk_at_start_of_file(
22679    executor: BackgroundExecutor,
22680    cx: &mut TestAppContext,
22681) {
22682    init_test(cx, |_| {});
22683    let mut cx = EditorTestContext::new(cx).await;
22684
22685    let diff_base = r#"
22686        a
22687        b
22688        c
22689        "#
22690    .unindent();
22691
22692    cx.set_state(
22693        &r#"
22694        ˇb
22695        c
22696        "#
22697        .unindent(),
22698    );
22699    cx.set_head_text(&diff_base);
22700    cx.update_editor(|editor, window, cx| {
22701        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
22702    });
22703    executor.run_until_parked();
22704
22705    let hunk_expanded = r#"
22706        - a
22707          ˇb
22708          c
22709        "#
22710    .unindent();
22711
22712    cx.assert_state_with_diff(hunk_expanded.clone());
22713
22714    let hunk_ranges = cx.update_editor(|editor, window, cx| {
22715        let snapshot = editor.snapshot(window, cx);
22716        let hunks = editor
22717            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
22718            .collect::<Vec<_>>();
22719        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
22720        hunks
22721            .into_iter()
22722            .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
22723            .collect::<Vec<_>>()
22724    });
22725    assert_eq!(hunk_ranges.len(), 1);
22726
22727    cx.update_editor(|editor, _, cx| {
22728        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
22729    });
22730    executor.run_until_parked();
22731
22732    let hunk_collapsed = r#"
22733          ˇb
22734          c
22735        "#
22736    .unindent();
22737
22738    cx.assert_state_with_diff(hunk_collapsed);
22739
22740    cx.update_editor(|editor, _, cx| {
22741        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
22742    });
22743    executor.run_until_parked();
22744
22745    cx.assert_state_with_diff(hunk_expanded);
22746}
22747
22748#[gpui::test]
22749async fn test_expand_first_line_diff_hunk_keeps_deleted_lines_visible(
22750    executor: BackgroundExecutor,
22751    cx: &mut TestAppContext,
22752) {
22753    init_test(cx, |_| {});
22754    let mut cx = EditorTestContext::new(cx).await;
22755
22756    cx.set_state("ˇnew\nsecond\nthird\n");
22757    cx.set_head_text("old\nsecond\nthird\n");
22758    cx.update_editor(|editor, window, cx| {
22759        editor.scroll(gpui::Point { x: 0., y: 0. }, None, window, cx);
22760    });
22761    executor.run_until_parked();
22762    assert_eq!(cx.update_editor(|e, _, cx| e.scroll_position(cx)).y, 0.0);
22763
22764    // Expanding a diff hunk at the first line inserts deleted lines above the first buffer line.
22765    cx.update_editor(|editor, window, cx| {
22766        let snapshot = editor.snapshot(window, cx);
22767        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
22768        let hunks = editor
22769            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
22770            .collect::<Vec<_>>();
22771        assert_eq!(hunks.len(), 1);
22772        let hunk_range = Anchor::range_in_buffer(excerpt_id, hunks[0].buffer_range.clone());
22773        editor.toggle_single_diff_hunk(hunk_range, cx)
22774    });
22775    executor.run_until_parked();
22776    cx.assert_state_with_diff("- old\n+ ˇnew\n  second\n  third\n".to_string());
22777
22778    // Keep the editor scrolled to the top so the full hunk remains visible.
22779    assert_eq!(cx.update_editor(|e, _, cx| e.scroll_position(cx)).y, 0.0);
22780}
22781
22782#[gpui::test]
22783async fn test_display_diff_hunks(cx: &mut TestAppContext) {
22784    init_test(cx, |_| {});
22785
22786    let fs = FakeFs::new(cx.executor());
22787    fs.insert_tree(
22788        path!("/test"),
22789        json!({
22790            ".git": {},
22791            "file-1": "ONE\n",
22792            "file-2": "TWO\n",
22793            "file-3": "THREE\n",
22794        }),
22795    )
22796    .await;
22797
22798    fs.set_head_for_repo(
22799        path!("/test/.git").as_ref(),
22800        &[
22801            ("file-1", "one\n".into()),
22802            ("file-2", "two\n".into()),
22803            ("file-3", "three\n".into()),
22804        ],
22805        "deadbeef",
22806    );
22807
22808    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
22809    let mut buffers = vec![];
22810    for i in 1..=3 {
22811        let buffer = project
22812            .update(cx, |project, cx| {
22813                let path = format!(path!("/test/file-{}"), i);
22814                project.open_local_buffer(path, cx)
22815            })
22816            .await
22817            .unwrap();
22818        buffers.push(buffer);
22819    }
22820
22821    let multibuffer = cx.new(|cx| {
22822        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
22823        multibuffer.set_all_diff_hunks_expanded(cx);
22824        for buffer in &buffers {
22825            let snapshot = buffer.read(cx).snapshot();
22826            multibuffer.set_excerpts_for_path(
22827                PathKey::with_sort_prefix(0, buffer.read(cx).file().unwrap().path().clone()),
22828                buffer.clone(),
22829                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
22830                2,
22831                cx,
22832            );
22833        }
22834        multibuffer
22835    });
22836
22837    let editor = cx.add_window(|window, cx| {
22838        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
22839    });
22840    cx.run_until_parked();
22841
22842    let snapshot = editor
22843        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
22844        .unwrap();
22845    let hunks = snapshot
22846        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
22847        .map(|hunk| match hunk {
22848            DisplayDiffHunk::Unfolded {
22849                display_row_range, ..
22850            } => display_row_range,
22851            DisplayDiffHunk::Folded { .. } => unreachable!(),
22852        })
22853        .collect::<Vec<_>>();
22854    assert_eq!(
22855        hunks,
22856        [
22857            DisplayRow(2)..DisplayRow(4),
22858            DisplayRow(7)..DisplayRow(9),
22859            DisplayRow(12)..DisplayRow(14),
22860        ]
22861    );
22862}
22863
22864#[gpui::test]
22865async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
22866    init_test(cx, |_| {});
22867
22868    let mut cx = EditorTestContext::new(cx).await;
22869    cx.set_head_text(indoc! { "
22870        one
22871        two
22872        three
22873        four
22874        five
22875        "
22876    });
22877    cx.set_index_text(indoc! { "
22878        one
22879        two
22880        three
22881        four
22882        five
22883        "
22884    });
22885    cx.set_state(indoc! {"
22886        one
22887        TWO
22888        ˇTHREE
22889        FOUR
22890        five
22891    "});
22892    cx.run_until_parked();
22893    cx.update_editor(|editor, window, cx| {
22894        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
22895    });
22896    cx.run_until_parked();
22897    cx.assert_index_text(Some(indoc! {"
22898        one
22899        TWO
22900        THREE
22901        FOUR
22902        five
22903    "}));
22904    cx.set_state(indoc! { "
22905        one
22906        TWO
22907        ˇTHREE-HUNDRED
22908        FOUR
22909        five
22910    "});
22911    cx.run_until_parked();
22912    cx.update_editor(|editor, window, cx| {
22913        let snapshot = editor.snapshot(window, cx);
22914        let hunks = editor
22915            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
22916            .collect::<Vec<_>>();
22917        assert_eq!(hunks.len(), 1);
22918        assert_eq!(
22919            hunks[0].status(),
22920            DiffHunkStatus {
22921                kind: DiffHunkStatusKind::Modified,
22922                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
22923            }
22924        );
22925
22926        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
22927    });
22928    cx.run_until_parked();
22929    cx.assert_index_text(Some(indoc! {"
22930        one
22931        TWO
22932        THREE-HUNDRED
22933        FOUR
22934        five
22935    "}));
22936}
22937
22938#[gpui::test]
22939fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
22940    init_test(cx, |_| {});
22941
22942    let editor = cx.add_window(|window, cx| {
22943        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
22944        build_editor(buffer, window, cx)
22945    });
22946
22947    let render_args = Arc::new(Mutex::new(None));
22948    let snapshot = editor
22949        .update(cx, |editor, window, cx| {
22950            let snapshot = editor.buffer().read(cx).snapshot(cx);
22951            let range =
22952                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
22953
22954            struct RenderArgs {
22955                row: MultiBufferRow,
22956                folded: bool,
22957                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
22958            }
22959
22960            let crease = Crease::inline(
22961                range,
22962                FoldPlaceholder::test(),
22963                {
22964                    let toggle_callback = render_args.clone();
22965                    move |row, folded, callback, _window, _cx| {
22966                        *toggle_callback.lock() = Some(RenderArgs {
22967                            row,
22968                            folded,
22969                            callback,
22970                        });
22971                        div()
22972                    }
22973                },
22974                |_row, _folded, _window, _cx| div(),
22975            );
22976
22977            editor.insert_creases(Some(crease), cx);
22978            let snapshot = editor.snapshot(window, cx);
22979            let _div =
22980                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
22981            snapshot
22982        })
22983        .unwrap();
22984
22985    let render_args = render_args.lock().take().unwrap();
22986    assert_eq!(render_args.row, MultiBufferRow(1));
22987    assert!(!render_args.folded);
22988    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
22989
22990    cx.update_window(*editor, |_, window, cx| {
22991        (render_args.callback)(true, window, cx)
22992    })
22993    .unwrap();
22994    let snapshot = editor
22995        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
22996        .unwrap();
22997    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
22998
22999    cx.update_window(*editor, |_, window, cx| {
23000        (render_args.callback)(false, window, cx)
23001    })
23002    .unwrap();
23003    let snapshot = editor
23004        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
23005        .unwrap();
23006    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
23007}
23008
23009#[gpui::test]
23010async fn test_input_text(cx: &mut TestAppContext) {
23011    init_test(cx, |_| {});
23012    let mut cx = EditorTestContext::new(cx).await;
23013
23014    cx.set_state(
23015        &r#"ˇone
23016        two
23017
23018        three
23019        fourˇ
23020        five
23021
23022        siˇx"#
23023            .unindent(),
23024    );
23025
23026    cx.dispatch_action(HandleInput(String::new()));
23027    cx.assert_editor_state(
23028        &r#"ˇone
23029        two
23030
23031        three
23032        fourˇ
23033        five
23034
23035        siˇx"#
23036            .unindent(),
23037    );
23038
23039    cx.dispatch_action(HandleInput("AAAA".to_string()));
23040    cx.assert_editor_state(
23041        &r#"AAAAˇone
23042        two
23043
23044        three
23045        fourAAAAˇ
23046        five
23047
23048        siAAAAˇx"#
23049            .unindent(),
23050    );
23051}
23052
23053#[gpui::test]
23054async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
23055    init_test(cx, |_| {});
23056
23057    let mut cx = EditorTestContext::new(cx).await;
23058    cx.set_state(
23059        r#"let foo = 1;
23060let foo = 2;
23061let foo = 3;
23062let fooˇ = 4;
23063let foo = 5;
23064let foo = 6;
23065let foo = 7;
23066let foo = 8;
23067let foo = 9;
23068let foo = 10;
23069let foo = 11;
23070let foo = 12;
23071let foo = 13;
23072let foo = 14;
23073let foo = 15;"#,
23074    );
23075
23076    cx.update_editor(|e, window, cx| {
23077        assert_eq!(
23078            e.next_scroll_position,
23079            NextScrollCursorCenterTopBottom::Center,
23080            "Default next scroll direction is center",
23081        );
23082
23083        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
23084        assert_eq!(
23085            e.next_scroll_position,
23086            NextScrollCursorCenterTopBottom::Top,
23087            "After center, next scroll direction should be top",
23088        );
23089
23090        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
23091        assert_eq!(
23092            e.next_scroll_position,
23093            NextScrollCursorCenterTopBottom::Bottom,
23094            "After top, next scroll direction should be bottom",
23095        );
23096
23097        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
23098        assert_eq!(
23099            e.next_scroll_position,
23100            NextScrollCursorCenterTopBottom::Center,
23101            "After bottom, scrolling should start over",
23102        );
23103
23104        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
23105        assert_eq!(
23106            e.next_scroll_position,
23107            NextScrollCursorCenterTopBottom::Top,
23108            "Scrolling continues if retriggered fast enough"
23109        );
23110    });
23111
23112    cx.executor()
23113        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
23114    cx.executor().run_until_parked();
23115    cx.update_editor(|e, _, _| {
23116        assert_eq!(
23117            e.next_scroll_position,
23118            NextScrollCursorCenterTopBottom::Center,
23119            "If scrolling is not triggered fast enough, it should reset"
23120        );
23121    });
23122}
23123
23124#[gpui::test]
23125async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
23126    init_test(cx, |_| {});
23127    let mut cx = EditorLspTestContext::new_rust(
23128        lsp::ServerCapabilities {
23129            definition_provider: Some(lsp::OneOf::Left(true)),
23130            references_provider: Some(lsp::OneOf::Left(true)),
23131            ..lsp::ServerCapabilities::default()
23132        },
23133        cx,
23134    )
23135    .await;
23136
23137    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
23138        let go_to_definition = cx
23139            .lsp
23140            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
23141                move |params, _| async move {
23142                    if empty_go_to_definition {
23143                        Ok(None)
23144                    } else {
23145                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
23146                            uri: params.text_document_position_params.text_document.uri,
23147                            range: lsp::Range::new(
23148                                lsp::Position::new(4, 3),
23149                                lsp::Position::new(4, 6),
23150                            ),
23151                        })))
23152                    }
23153                },
23154            );
23155        let references = cx
23156            .lsp
23157            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
23158                Ok(Some(vec![lsp::Location {
23159                    uri: params.text_document_position.text_document.uri,
23160                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
23161                }]))
23162            });
23163        (go_to_definition, references)
23164    };
23165
23166    cx.set_state(
23167        &r#"fn one() {
23168            let mut a = ˇtwo();
23169        }
23170
23171        fn two() {}"#
23172            .unindent(),
23173    );
23174    set_up_lsp_handlers(false, &mut cx);
23175    let navigated = cx
23176        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
23177        .await
23178        .expect("Failed to navigate to definition");
23179    assert_eq!(
23180        navigated,
23181        Navigated::Yes,
23182        "Should have navigated to definition from the GetDefinition response"
23183    );
23184    cx.assert_editor_state(
23185        &r#"fn one() {
23186            let mut a = two();
23187        }
23188
23189        fn «twoˇ»() {}"#
23190            .unindent(),
23191    );
23192
23193    let editors = cx.update_workspace(|workspace, _, cx| {
23194        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
23195    });
23196    cx.update_editor(|_, _, test_editor_cx| {
23197        assert_eq!(
23198            editors.len(),
23199            1,
23200            "Initially, only one, test, editor should be open in the workspace"
23201        );
23202        assert_eq!(
23203            test_editor_cx.entity(),
23204            editors.last().expect("Asserted len is 1").clone()
23205        );
23206    });
23207
23208    set_up_lsp_handlers(true, &mut cx);
23209    let navigated = cx
23210        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
23211        .await
23212        .expect("Failed to navigate to lookup references");
23213    assert_eq!(
23214        navigated,
23215        Navigated::Yes,
23216        "Should have navigated to references as a fallback after empty GoToDefinition response"
23217    );
23218    // We should not change the selections in the existing file,
23219    // if opening another milti buffer with the references
23220    cx.assert_editor_state(
23221        &r#"fn one() {
23222            let mut a = two();
23223        }
23224
23225        fn «twoˇ»() {}"#
23226            .unindent(),
23227    );
23228    let editors = cx.update_workspace(|workspace, _, cx| {
23229        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
23230    });
23231    cx.update_editor(|_, _, test_editor_cx| {
23232        assert_eq!(
23233            editors.len(),
23234            2,
23235            "After falling back to references search, we open a new editor with the results"
23236        );
23237        let references_fallback_text = editors
23238            .into_iter()
23239            .find(|new_editor| *new_editor != test_editor_cx.entity())
23240            .expect("Should have one non-test editor now")
23241            .read(test_editor_cx)
23242            .text(test_editor_cx);
23243        assert_eq!(
23244            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
23245            "Should use the range from the references response and not the GoToDefinition one"
23246        );
23247    });
23248}
23249
23250#[gpui::test]
23251async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
23252    init_test(cx, |_| {});
23253    cx.update(|cx| {
23254        let mut editor_settings = EditorSettings::get_global(cx).clone();
23255        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
23256        EditorSettings::override_global(editor_settings, cx);
23257    });
23258    let mut cx = EditorLspTestContext::new_rust(
23259        lsp::ServerCapabilities {
23260            definition_provider: Some(lsp::OneOf::Left(true)),
23261            references_provider: Some(lsp::OneOf::Left(true)),
23262            ..lsp::ServerCapabilities::default()
23263        },
23264        cx,
23265    )
23266    .await;
23267    let original_state = r#"fn one() {
23268        let mut a = ˇtwo();
23269    }
23270
23271    fn two() {}"#
23272        .unindent();
23273    cx.set_state(&original_state);
23274
23275    let mut go_to_definition = cx
23276        .lsp
23277        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
23278            move |_, _| async move { Ok(None) },
23279        );
23280    let _references = cx
23281        .lsp
23282        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
23283            panic!("Should not call for references with no go to definition fallback")
23284        });
23285
23286    let navigated = cx
23287        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
23288        .await
23289        .expect("Failed to navigate to lookup references");
23290    go_to_definition
23291        .next()
23292        .await
23293        .expect("Should have called the go_to_definition handler");
23294
23295    assert_eq!(
23296        navigated,
23297        Navigated::No,
23298        "Should have navigated to references as a fallback after empty GoToDefinition response"
23299    );
23300    cx.assert_editor_state(&original_state);
23301    let editors = cx.update_workspace(|workspace, _, cx| {
23302        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
23303    });
23304    cx.update_editor(|_, _, _| {
23305        assert_eq!(
23306            editors.len(),
23307            1,
23308            "After unsuccessful fallback, no other editor should have been opened"
23309        );
23310    });
23311}
23312
23313#[gpui::test]
23314async fn test_find_all_references_editor_reuse(cx: &mut TestAppContext) {
23315    init_test(cx, |_| {});
23316    let mut cx = EditorLspTestContext::new_rust(
23317        lsp::ServerCapabilities {
23318            references_provider: Some(lsp::OneOf::Left(true)),
23319            ..lsp::ServerCapabilities::default()
23320        },
23321        cx,
23322    )
23323    .await;
23324
23325    cx.set_state(
23326        &r#"
23327        fn one() {
23328            let mut a = two();
23329        }
23330
23331        fn ˇtwo() {}"#
23332            .unindent(),
23333    );
23334    cx.lsp
23335        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
23336            Ok(Some(vec![
23337                lsp::Location {
23338                    uri: params.text_document_position.text_document.uri.clone(),
23339                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
23340                },
23341                lsp::Location {
23342                    uri: params.text_document_position.text_document.uri,
23343                    range: lsp::Range::new(lsp::Position::new(4, 4), lsp::Position::new(4, 7)),
23344                },
23345            ]))
23346        });
23347    let navigated = cx
23348        .update_editor(|editor, window, cx| {
23349            editor.find_all_references(&FindAllReferences::default(), window, cx)
23350        })
23351        .unwrap()
23352        .await
23353        .expect("Failed to navigate to references");
23354    assert_eq!(
23355        navigated,
23356        Navigated::Yes,
23357        "Should have navigated to references from the FindAllReferences response"
23358    );
23359    cx.assert_editor_state(
23360        &r#"fn one() {
23361            let mut a = two();
23362        }
23363
23364        fn ˇtwo() {}"#
23365            .unindent(),
23366    );
23367
23368    let editors = cx.update_workspace(|workspace, _, cx| {
23369        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
23370    });
23371    cx.update_editor(|_, _, _| {
23372        assert_eq!(editors.len(), 2, "We should have opened a new multibuffer");
23373    });
23374
23375    cx.set_state(
23376        &r#"fn one() {
23377            let mut a = ˇtwo();
23378        }
23379
23380        fn two() {}"#
23381            .unindent(),
23382    );
23383    let navigated = cx
23384        .update_editor(|editor, window, cx| {
23385            editor.find_all_references(&FindAllReferences::default(), window, cx)
23386        })
23387        .unwrap()
23388        .await
23389        .expect("Failed to navigate to references");
23390    assert_eq!(
23391        navigated,
23392        Navigated::Yes,
23393        "Should have navigated to references from the FindAllReferences response"
23394    );
23395    cx.assert_editor_state(
23396        &r#"fn one() {
23397            let mut a = ˇtwo();
23398        }
23399
23400        fn two() {}"#
23401            .unindent(),
23402    );
23403    let editors = cx.update_workspace(|workspace, _, cx| {
23404        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
23405    });
23406    cx.update_editor(|_, _, _| {
23407        assert_eq!(
23408            editors.len(),
23409            2,
23410            "should have re-used the previous multibuffer"
23411        );
23412    });
23413
23414    cx.set_state(
23415        &r#"fn one() {
23416            let mut a = ˇtwo();
23417        }
23418        fn three() {}
23419        fn two() {}"#
23420            .unindent(),
23421    );
23422    cx.lsp
23423        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
23424            Ok(Some(vec![
23425                lsp::Location {
23426                    uri: params.text_document_position.text_document.uri.clone(),
23427                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
23428                },
23429                lsp::Location {
23430                    uri: params.text_document_position.text_document.uri,
23431                    range: lsp::Range::new(lsp::Position::new(5, 4), lsp::Position::new(5, 7)),
23432                },
23433            ]))
23434        });
23435    let navigated = cx
23436        .update_editor(|editor, window, cx| {
23437            editor.find_all_references(&FindAllReferences::default(), window, cx)
23438        })
23439        .unwrap()
23440        .await
23441        .expect("Failed to navigate to references");
23442    assert_eq!(
23443        navigated,
23444        Navigated::Yes,
23445        "Should have navigated to references from the FindAllReferences response"
23446    );
23447    cx.assert_editor_state(
23448        &r#"fn one() {
23449                let mut a = ˇtwo();
23450            }
23451            fn three() {}
23452            fn two() {}"#
23453            .unindent(),
23454    );
23455    let editors = cx.update_workspace(|workspace, _, cx| {
23456        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
23457    });
23458    cx.update_editor(|_, _, _| {
23459        assert_eq!(
23460            editors.len(),
23461            3,
23462            "should have used a new multibuffer as offsets changed"
23463        );
23464    });
23465}
23466#[gpui::test]
23467async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
23468    init_test(cx, |_| {});
23469
23470    let language = Arc::new(Language::new(
23471        LanguageConfig::default(),
23472        Some(tree_sitter_rust::LANGUAGE.into()),
23473    ));
23474
23475    let text = r#"
23476        #[cfg(test)]
23477        mod tests() {
23478            #[test]
23479            fn runnable_1() {
23480                let a = 1;
23481            }
23482
23483            #[test]
23484            fn runnable_2() {
23485                let a = 1;
23486                let b = 2;
23487            }
23488        }
23489    "#
23490    .unindent();
23491
23492    let fs = FakeFs::new(cx.executor());
23493    fs.insert_file("/file.rs", Default::default()).await;
23494
23495    let project = Project::test(fs, ["/a".as_ref()], cx).await;
23496    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23497    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23498    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
23499    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
23500
23501    let editor = cx.new_window_entity(|window, cx| {
23502        Editor::new(
23503            EditorMode::full(),
23504            multi_buffer,
23505            Some(project.clone()),
23506            window,
23507            cx,
23508        )
23509    });
23510
23511    editor.update_in(cx, |editor, window, cx| {
23512        let snapshot = editor.buffer().read(cx).snapshot(cx);
23513        editor.tasks.insert(
23514            (buffer.read(cx).remote_id(), 3),
23515            RunnableTasks {
23516                templates: vec![],
23517                offset: snapshot.anchor_before(MultiBufferOffset(43)),
23518                column: 0,
23519                extra_variables: HashMap::default(),
23520                context_range: BufferOffset(43)..BufferOffset(85),
23521            },
23522        );
23523        editor.tasks.insert(
23524            (buffer.read(cx).remote_id(), 8),
23525            RunnableTasks {
23526                templates: vec![],
23527                offset: snapshot.anchor_before(MultiBufferOffset(86)),
23528                column: 0,
23529                extra_variables: HashMap::default(),
23530                context_range: BufferOffset(86)..BufferOffset(191),
23531            },
23532        );
23533
23534        // Test finding task when cursor is inside function body
23535        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23536            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
23537        });
23538        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
23539        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
23540
23541        // Test finding task when cursor is on function name
23542        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23543            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
23544        });
23545        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
23546        assert_eq!(row, 8, "Should find task when cursor is on function name");
23547    });
23548}
23549
23550#[gpui::test]
23551async fn test_folding_buffers(cx: &mut TestAppContext) {
23552    init_test(cx, |_| {});
23553
23554    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
23555    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
23556    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
23557
23558    let fs = FakeFs::new(cx.executor());
23559    fs.insert_tree(
23560        path!("/a"),
23561        json!({
23562            "first.rs": sample_text_1,
23563            "second.rs": sample_text_2,
23564            "third.rs": sample_text_3,
23565        }),
23566    )
23567    .await;
23568    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23569    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23570    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23571    let worktree = project.update(cx, |project, cx| {
23572        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
23573        assert_eq!(worktrees.len(), 1);
23574        worktrees.pop().unwrap()
23575    });
23576    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
23577
23578    let buffer_1 = project
23579        .update(cx, |project, cx| {
23580            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
23581        })
23582        .await
23583        .unwrap();
23584    let buffer_2 = project
23585        .update(cx, |project, cx| {
23586            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
23587        })
23588        .await
23589        .unwrap();
23590    let buffer_3 = project
23591        .update(cx, |project, cx| {
23592            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
23593        })
23594        .await
23595        .unwrap();
23596
23597    let multi_buffer = cx.new(|cx| {
23598        let mut multi_buffer = MultiBuffer::new(ReadWrite);
23599        multi_buffer.push_excerpts(
23600            buffer_1.clone(),
23601            [
23602                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
23603                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
23604                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
23605            ],
23606            cx,
23607        );
23608        multi_buffer.push_excerpts(
23609            buffer_2.clone(),
23610            [
23611                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
23612                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
23613                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
23614            ],
23615            cx,
23616        );
23617        multi_buffer.push_excerpts(
23618            buffer_3.clone(),
23619            [
23620                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
23621                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
23622                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
23623            ],
23624            cx,
23625        );
23626        multi_buffer
23627    });
23628    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
23629        Editor::new(
23630            EditorMode::full(),
23631            multi_buffer.clone(),
23632            Some(project.clone()),
23633            window,
23634            cx,
23635        )
23636    });
23637
23638    assert_eq!(
23639        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23640        "\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",
23641    );
23642
23643    multi_buffer_editor.update(cx, |editor, cx| {
23644        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
23645    });
23646    assert_eq!(
23647        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23648        "\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",
23649        "After folding the first buffer, its text should not be displayed"
23650    );
23651
23652    multi_buffer_editor.update(cx, |editor, cx| {
23653        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
23654    });
23655    assert_eq!(
23656        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23657        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
23658        "After folding the second buffer, its text should not be displayed"
23659    );
23660
23661    multi_buffer_editor.update(cx, |editor, cx| {
23662        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
23663    });
23664    assert_eq!(
23665        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23666        "\n\n\n\n\n",
23667        "After folding the third buffer, its text should not be displayed"
23668    );
23669
23670    // Emulate selection inside the fold logic, that should work
23671    multi_buffer_editor.update_in(cx, |editor, window, cx| {
23672        editor
23673            .snapshot(window, cx)
23674            .next_line_boundary(Point::new(0, 4));
23675    });
23676
23677    multi_buffer_editor.update(cx, |editor, cx| {
23678        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
23679    });
23680    assert_eq!(
23681        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23682        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
23683        "After unfolding the second buffer, its text should be displayed"
23684    );
23685
23686    // Typing inside of buffer 1 causes that buffer to be unfolded.
23687    multi_buffer_editor.update_in(cx, |editor, window, cx| {
23688        assert_eq!(
23689            multi_buffer
23690                .read(cx)
23691                .snapshot(cx)
23692                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
23693                .collect::<String>(),
23694            "bbbb"
23695        );
23696        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23697            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
23698        });
23699        editor.handle_input("B", window, cx);
23700    });
23701
23702    assert_eq!(
23703        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23704        "\n\naaaa\nBbbbb\ncccc\n\n\nffff\ngggg\n\n\njjjj\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
23705        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
23706    );
23707
23708    multi_buffer_editor.update(cx, |editor, cx| {
23709        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
23710    });
23711    assert_eq!(
23712        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23713        "\n\naaaa\nBbbbb\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",
23714        "After unfolding the all buffers, all original text should be displayed"
23715    );
23716}
23717
23718#[gpui::test]
23719async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
23720    init_test(cx, |_| {});
23721
23722    let sample_text_1 = "1111\n2222\n3333".to_string();
23723    let sample_text_2 = "4444\n5555\n6666".to_string();
23724    let sample_text_3 = "7777\n8888\n9999".to_string();
23725
23726    let fs = FakeFs::new(cx.executor());
23727    fs.insert_tree(
23728        path!("/a"),
23729        json!({
23730            "first.rs": sample_text_1,
23731            "second.rs": sample_text_2,
23732            "third.rs": sample_text_3,
23733        }),
23734    )
23735    .await;
23736    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23737    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23738    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23739    let worktree = project.update(cx, |project, cx| {
23740        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
23741        assert_eq!(worktrees.len(), 1);
23742        worktrees.pop().unwrap()
23743    });
23744    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
23745
23746    let buffer_1 = project
23747        .update(cx, |project, cx| {
23748            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
23749        })
23750        .await
23751        .unwrap();
23752    let buffer_2 = project
23753        .update(cx, |project, cx| {
23754            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
23755        })
23756        .await
23757        .unwrap();
23758    let buffer_3 = project
23759        .update(cx, |project, cx| {
23760            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
23761        })
23762        .await
23763        .unwrap();
23764
23765    let multi_buffer = cx.new(|cx| {
23766        let mut multi_buffer = MultiBuffer::new(ReadWrite);
23767        multi_buffer.push_excerpts(
23768            buffer_1.clone(),
23769            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
23770            cx,
23771        );
23772        multi_buffer.push_excerpts(
23773            buffer_2.clone(),
23774            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
23775            cx,
23776        );
23777        multi_buffer.push_excerpts(
23778            buffer_3.clone(),
23779            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
23780            cx,
23781        );
23782        multi_buffer
23783    });
23784
23785    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
23786        Editor::new(
23787            EditorMode::full(),
23788            multi_buffer,
23789            Some(project.clone()),
23790            window,
23791            cx,
23792        )
23793    });
23794
23795    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
23796    assert_eq!(
23797        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23798        full_text,
23799    );
23800
23801    multi_buffer_editor.update(cx, |editor, cx| {
23802        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
23803    });
23804    assert_eq!(
23805        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23806        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
23807        "After folding the first buffer, its text should not be displayed"
23808    );
23809
23810    multi_buffer_editor.update(cx, |editor, cx| {
23811        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
23812    });
23813
23814    assert_eq!(
23815        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23816        "\n\n\n\n\n\n7777\n8888\n9999",
23817        "After folding the second buffer, its text should not be displayed"
23818    );
23819
23820    multi_buffer_editor.update(cx, |editor, cx| {
23821        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
23822    });
23823    assert_eq!(
23824        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23825        "\n\n\n\n\n",
23826        "After folding the third buffer, its text should not be displayed"
23827    );
23828
23829    multi_buffer_editor.update(cx, |editor, cx| {
23830        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
23831    });
23832    assert_eq!(
23833        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23834        "\n\n\n\n4444\n5555\n6666\n\n",
23835        "After unfolding the second buffer, its text should be displayed"
23836    );
23837
23838    multi_buffer_editor.update(cx, |editor, cx| {
23839        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
23840    });
23841    assert_eq!(
23842        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23843        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
23844        "After unfolding the first buffer, its text should be displayed"
23845    );
23846
23847    multi_buffer_editor.update(cx, |editor, cx| {
23848        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
23849    });
23850    assert_eq!(
23851        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23852        full_text,
23853        "After unfolding all buffers, all original text should be displayed"
23854    );
23855}
23856
23857#[gpui::test]
23858async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
23859    init_test(cx, |_| {});
23860
23861    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
23862
23863    let fs = FakeFs::new(cx.executor());
23864    fs.insert_tree(
23865        path!("/a"),
23866        json!({
23867            "main.rs": sample_text,
23868        }),
23869    )
23870    .await;
23871    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23872    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23873    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23874    let worktree = project.update(cx, |project, cx| {
23875        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
23876        assert_eq!(worktrees.len(), 1);
23877        worktrees.pop().unwrap()
23878    });
23879    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
23880
23881    let buffer_1 = project
23882        .update(cx, |project, cx| {
23883            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23884        })
23885        .await
23886        .unwrap();
23887
23888    let multi_buffer = cx.new(|cx| {
23889        let mut multi_buffer = MultiBuffer::new(ReadWrite);
23890        multi_buffer.push_excerpts(
23891            buffer_1.clone(),
23892            [ExcerptRange::new(
23893                Point::new(0, 0)
23894                    ..Point::new(
23895                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
23896                        0,
23897                    ),
23898            )],
23899            cx,
23900        );
23901        multi_buffer
23902    });
23903    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
23904        Editor::new(
23905            EditorMode::full(),
23906            multi_buffer,
23907            Some(project.clone()),
23908            window,
23909            cx,
23910        )
23911    });
23912
23913    let selection_range = Point::new(1, 0)..Point::new(2, 0);
23914    multi_buffer_editor.update_in(cx, |editor, window, cx| {
23915        enum TestHighlight {}
23916        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
23917        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
23918        editor.highlight_text::<TestHighlight>(
23919            vec![highlight_range.clone()],
23920            HighlightStyle::color(Hsla::green()),
23921            cx,
23922        );
23923        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23924            s.select_ranges(Some(highlight_range))
23925        });
23926    });
23927
23928    let full_text = format!("\n\n{sample_text}");
23929    assert_eq!(
23930        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23931        full_text,
23932    );
23933}
23934
23935#[gpui::test]
23936async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
23937    init_test(cx, |_| {});
23938    cx.update(|cx| {
23939        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
23940            "keymaps/default-linux.json",
23941            cx,
23942        )
23943        .unwrap();
23944        cx.bind_keys(default_key_bindings);
23945    });
23946
23947    let (editor, cx) = cx.add_window_view(|window, cx| {
23948        let multi_buffer = MultiBuffer::build_multi(
23949            [
23950                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
23951                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
23952                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
23953                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
23954            ],
23955            cx,
23956        );
23957        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
23958
23959        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
23960        // fold all but the second buffer, so that we test navigating between two
23961        // adjacent folded buffers, as well as folded buffers at the start and
23962        // end the multibuffer
23963        editor.fold_buffer(buffer_ids[0], cx);
23964        editor.fold_buffer(buffer_ids[2], cx);
23965        editor.fold_buffer(buffer_ids[3], cx);
23966
23967        editor
23968    });
23969    cx.simulate_resize(size(px(1000.), px(1000.)));
23970
23971    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
23972    cx.assert_excerpts_with_selections(indoc! {"
23973        [EXCERPT]
23974        ˇ[FOLDED]
23975        [EXCERPT]
23976        a1
23977        b1
23978        [EXCERPT]
23979        [FOLDED]
23980        [EXCERPT]
23981        [FOLDED]
23982        "
23983    });
23984    cx.simulate_keystroke("down");
23985    cx.assert_excerpts_with_selections(indoc! {"
23986        [EXCERPT]
23987        [FOLDED]
23988        [EXCERPT]
23989        ˇa1
23990        b1
23991        [EXCERPT]
23992        [FOLDED]
23993        [EXCERPT]
23994        [FOLDED]
23995        "
23996    });
23997    cx.simulate_keystroke("down");
23998    cx.assert_excerpts_with_selections(indoc! {"
23999        [EXCERPT]
24000        [FOLDED]
24001        [EXCERPT]
24002        a1
24003        ˇb1
24004        [EXCERPT]
24005        [FOLDED]
24006        [EXCERPT]
24007        [FOLDED]
24008        "
24009    });
24010    cx.simulate_keystroke("down");
24011    cx.assert_excerpts_with_selections(indoc! {"
24012        [EXCERPT]
24013        [FOLDED]
24014        [EXCERPT]
24015        a1
24016        b1
24017        ˇ[EXCERPT]
24018        [FOLDED]
24019        [EXCERPT]
24020        [FOLDED]
24021        "
24022    });
24023    cx.simulate_keystroke("down");
24024    cx.assert_excerpts_with_selections(indoc! {"
24025        [EXCERPT]
24026        [FOLDED]
24027        [EXCERPT]
24028        a1
24029        b1
24030        [EXCERPT]
24031        ˇ[FOLDED]
24032        [EXCERPT]
24033        [FOLDED]
24034        "
24035    });
24036    for _ in 0..5 {
24037        cx.simulate_keystroke("down");
24038        cx.assert_excerpts_with_selections(indoc! {"
24039            [EXCERPT]
24040            [FOLDED]
24041            [EXCERPT]
24042            a1
24043            b1
24044            [EXCERPT]
24045            [FOLDED]
24046            [EXCERPT]
24047            ˇ[FOLDED]
24048            "
24049        });
24050    }
24051
24052    cx.simulate_keystroke("up");
24053    cx.assert_excerpts_with_selections(indoc! {"
24054        [EXCERPT]
24055        [FOLDED]
24056        [EXCERPT]
24057        a1
24058        b1
24059        [EXCERPT]
24060        ˇ[FOLDED]
24061        [EXCERPT]
24062        [FOLDED]
24063        "
24064    });
24065    cx.simulate_keystroke("up");
24066    cx.assert_excerpts_with_selections(indoc! {"
24067        [EXCERPT]
24068        [FOLDED]
24069        [EXCERPT]
24070        a1
24071        b1
24072        ˇ[EXCERPT]
24073        [FOLDED]
24074        [EXCERPT]
24075        [FOLDED]
24076        "
24077    });
24078    cx.simulate_keystroke("up");
24079    cx.assert_excerpts_with_selections(indoc! {"
24080        [EXCERPT]
24081        [FOLDED]
24082        [EXCERPT]
24083        a1
24084        ˇb1
24085        [EXCERPT]
24086        [FOLDED]
24087        [EXCERPT]
24088        [FOLDED]
24089        "
24090    });
24091    cx.simulate_keystroke("up");
24092    cx.assert_excerpts_with_selections(indoc! {"
24093        [EXCERPT]
24094        [FOLDED]
24095        [EXCERPT]
24096        ˇa1
24097        b1
24098        [EXCERPT]
24099        [FOLDED]
24100        [EXCERPT]
24101        [FOLDED]
24102        "
24103    });
24104    for _ in 0..5 {
24105        cx.simulate_keystroke("up");
24106        cx.assert_excerpts_with_selections(indoc! {"
24107            [EXCERPT]
24108            ˇ[FOLDED]
24109            [EXCERPT]
24110            a1
24111            b1
24112            [EXCERPT]
24113            [FOLDED]
24114            [EXCERPT]
24115            [FOLDED]
24116            "
24117        });
24118    }
24119}
24120
24121#[gpui::test]
24122async fn test_edit_prediction_text(cx: &mut TestAppContext) {
24123    init_test(cx, |_| {});
24124
24125    // Simple insertion
24126    assert_highlighted_edits(
24127        "Hello, world!",
24128        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
24129        true,
24130        cx,
24131        |highlighted_edits, cx| {
24132            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
24133            assert_eq!(highlighted_edits.highlights.len(), 1);
24134            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
24135            assert_eq!(
24136                highlighted_edits.highlights[0].1.background_color,
24137                Some(cx.theme().status().created_background)
24138            );
24139        },
24140    )
24141    .await;
24142
24143    // Replacement
24144    assert_highlighted_edits(
24145        "This is a test.",
24146        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
24147        false,
24148        cx,
24149        |highlighted_edits, cx| {
24150            assert_eq!(highlighted_edits.text, "That is a test.");
24151            assert_eq!(highlighted_edits.highlights.len(), 1);
24152            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
24153            assert_eq!(
24154                highlighted_edits.highlights[0].1.background_color,
24155                Some(cx.theme().status().created_background)
24156            );
24157        },
24158    )
24159    .await;
24160
24161    // Multiple edits
24162    assert_highlighted_edits(
24163        "Hello, world!",
24164        vec![
24165            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
24166            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
24167        ],
24168        false,
24169        cx,
24170        |highlighted_edits, cx| {
24171            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
24172            assert_eq!(highlighted_edits.highlights.len(), 2);
24173            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
24174            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
24175            assert_eq!(
24176                highlighted_edits.highlights[0].1.background_color,
24177                Some(cx.theme().status().created_background)
24178            );
24179            assert_eq!(
24180                highlighted_edits.highlights[1].1.background_color,
24181                Some(cx.theme().status().created_background)
24182            );
24183        },
24184    )
24185    .await;
24186
24187    // Multiple lines with edits
24188    assert_highlighted_edits(
24189        "First line\nSecond line\nThird line\nFourth line",
24190        vec![
24191            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
24192            (
24193                Point::new(2, 0)..Point::new(2, 10),
24194                "New third line".to_string(),
24195            ),
24196            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
24197        ],
24198        false,
24199        cx,
24200        |highlighted_edits, cx| {
24201            assert_eq!(
24202                highlighted_edits.text,
24203                "Second modified\nNew third line\nFourth updated line"
24204            );
24205            assert_eq!(highlighted_edits.highlights.len(), 3);
24206            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
24207            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
24208            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
24209            for highlight in &highlighted_edits.highlights {
24210                assert_eq!(
24211                    highlight.1.background_color,
24212                    Some(cx.theme().status().created_background)
24213                );
24214            }
24215        },
24216    )
24217    .await;
24218}
24219
24220#[gpui::test]
24221async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
24222    init_test(cx, |_| {});
24223
24224    // Deletion
24225    assert_highlighted_edits(
24226        "Hello, world!",
24227        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
24228        true,
24229        cx,
24230        |highlighted_edits, cx| {
24231            assert_eq!(highlighted_edits.text, "Hello, world!");
24232            assert_eq!(highlighted_edits.highlights.len(), 1);
24233            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
24234            assert_eq!(
24235                highlighted_edits.highlights[0].1.background_color,
24236                Some(cx.theme().status().deleted_background)
24237            );
24238        },
24239    )
24240    .await;
24241
24242    // Insertion
24243    assert_highlighted_edits(
24244        "Hello, world!",
24245        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
24246        true,
24247        cx,
24248        |highlighted_edits, cx| {
24249            assert_eq!(highlighted_edits.highlights.len(), 1);
24250            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
24251            assert_eq!(
24252                highlighted_edits.highlights[0].1.background_color,
24253                Some(cx.theme().status().created_background)
24254            );
24255        },
24256    )
24257    .await;
24258}
24259
24260async fn assert_highlighted_edits(
24261    text: &str,
24262    edits: Vec<(Range<Point>, String)>,
24263    include_deletions: bool,
24264    cx: &mut TestAppContext,
24265    assertion_fn: impl Fn(HighlightedText, &App),
24266) {
24267    let window = cx.add_window(|window, cx| {
24268        let buffer = MultiBuffer::build_simple(text, cx);
24269        Editor::new(EditorMode::full(), buffer, None, window, cx)
24270    });
24271    let cx = &mut VisualTestContext::from_window(*window, cx);
24272
24273    let (buffer, snapshot) = window
24274        .update(cx, |editor, _window, cx| {
24275            (
24276                editor.buffer().clone(),
24277                editor.buffer().read(cx).snapshot(cx),
24278            )
24279        })
24280        .unwrap();
24281
24282    let edits = edits
24283        .into_iter()
24284        .map(|(range, edit)| {
24285            (
24286                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
24287                edit,
24288            )
24289        })
24290        .collect::<Vec<_>>();
24291
24292    let text_anchor_edits = edits
24293        .clone()
24294        .into_iter()
24295        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit.into()))
24296        .collect::<Vec<_>>();
24297
24298    let edit_preview = window
24299        .update(cx, |_, _window, cx| {
24300            buffer
24301                .read(cx)
24302                .as_singleton()
24303                .unwrap()
24304                .read(cx)
24305                .preview_edits(text_anchor_edits.into(), cx)
24306        })
24307        .unwrap()
24308        .await;
24309
24310    cx.update(|_window, cx| {
24311        let highlighted_edits = edit_prediction_edit_text(
24312            snapshot.as_singleton().unwrap().2,
24313            &edits,
24314            &edit_preview,
24315            include_deletions,
24316            cx,
24317        );
24318        assertion_fn(highlighted_edits, cx)
24319    });
24320}
24321
24322#[track_caller]
24323fn assert_breakpoint(
24324    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
24325    path: &Arc<Path>,
24326    expected: Vec<(u32, Breakpoint)>,
24327) {
24328    if expected.is_empty() {
24329        assert!(!breakpoints.contains_key(path), "{}", path.display());
24330    } else {
24331        let mut breakpoint = breakpoints
24332            .get(path)
24333            .unwrap()
24334            .iter()
24335            .map(|breakpoint| {
24336                (
24337                    breakpoint.row,
24338                    Breakpoint {
24339                        message: breakpoint.message.clone(),
24340                        state: breakpoint.state,
24341                        condition: breakpoint.condition.clone(),
24342                        hit_condition: breakpoint.hit_condition.clone(),
24343                    },
24344                )
24345            })
24346            .collect::<Vec<_>>();
24347
24348        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
24349
24350        assert_eq!(expected, breakpoint);
24351    }
24352}
24353
24354fn add_log_breakpoint_at_cursor(
24355    editor: &mut Editor,
24356    log_message: &str,
24357    window: &mut Window,
24358    cx: &mut Context<Editor>,
24359) {
24360    let (anchor, bp) = editor
24361        .breakpoints_at_cursors(window, cx)
24362        .first()
24363        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
24364        .unwrap_or_else(|| {
24365            let snapshot = editor.snapshot(window, cx);
24366            let cursor_position: Point =
24367                editor.selections.newest(&snapshot.display_snapshot).head();
24368
24369            let breakpoint_position = snapshot
24370                .buffer_snapshot()
24371                .anchor_before(Point::new(cursor_position.row, 0));
24372
24373            (breakpoint_position, Breakpoint::new_log(log_message))
24374        });
24375
24376    editor.edit_breakpoint_at_anchor(
24377        anchor,
24378        bp,
24379        BreakpointEditAction::EditLogMessage(log_message.into()),
24380        cx,
24381    );
24382}
24383
24384#[gpui::test]
24385async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
24386    init_test(cx, |_| {});
24387
24388    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
24389    let fs = FakeFs::new(cx.executor());
24390    fs.insert_tree(
24391        path!("/a"),
24392        json!({
24393            "main.rs": sample_text,
24394        }),
24395    )
24396    .await;
24397    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24398    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24399    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24400
24401    let fs = FakeFs::new(cx.executor());
24402    fs.insert_tree(
24403        path!("/a"),
24404        json!({
24405            "main.rs": sample_text,
24406        }),
24407    )
24408    .await;
24409    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24410    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24411    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24412    let worktree_id = workspace
24413        .update(cx, |workspace, _window, cx| {
24414            workspace.project().update(cx, |project, cx| {
24415                project.worktrees(cx).next().unwrap().read(cx).id()
24416            })
24417        })
24418        .unwrap();
24419
24420    let buffer = project
24421        .update(cx, |project, cx| {
24422            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
24423        })
24424        .await
24425        .unwrap();
24426
24427    let (editor, cx) = cx.add_window_view(|window, cx| {
24428        Editor::new(
24429            EditorMode::full(),
24430            MultiBuffer::build_from_buffer(buffer, cx),
24431            Some(project.clone()),
24432            window,
24433            cx,
24434        )
24435    });
24436
24437    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
24438    let abs_path = project.read_with(cx, |project, cx| {
24439        project
24440            .absolute_path(&project_path, cx)
24441            .map(Arc::from)
24442            .unwrap()
24443    });
24444
24445    // assert we can add breakpoint on the first line
24446    editor.update_in(cx, |editor, window, cx| {
24447        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24448        editor.move_to_end(&MoveToEnd, window, cx);
24449        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24450    });
24451
24452    let breakpoints = editor.update(cx, |editor, cx| {
24453        editor
24454            .breakpoint_store()
24455            .as_ref()
24456            .unwrap()
24457            .read(cx)
24458            .all_source_breakpoints(cx)
24459    });
24460
24461    assert_eq!(1, breakpoints.len());
24462    assert_breakpoint(
24463        &breakpoints,
24464        &abs_path,
24465        vec![
24466            (0, Breakpoint::new_standard()),
24467            (3, Breakpoint::new_standard()),
24468        ],
24469    );
24470
24471    editor.update_in(cx, |editor, window, cx| {
24472        editor.move_to_beginning(&MoveToBeginning, window, cx);
24473        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24474    });
24475
24476    let breakpoints = editor.update(cx, |editor, cx| {
24477        editor
24478            .breakpoint_store()
24479            .as_ref()
24480            .unwrap()
24481            .read(cx)
24482            .all_source_breakpoints(cx)
24483    });
24484
24485    assert_eq!(1, breakpoints.len());
24486    assert_breakpoint(
24487        &breakpoints,
24488        &abs_path,
24489        vec![(3, Breakpoint::new_standard())],
24490    );
24491
24492    editor.update_in(cx, |editor, window, cx| {
24493        editor.move_to_end(&MoveToEnd, window, cx);
24494        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24495    });
24496
24497    let breakpoints = editor.update(cx, |editor, cx| {
24498        editor
24499            .breakpoint_store()
24500            .as_ref()
24501            .unwrap()
24502            .read(cx)
24503            .all_source_breakpoints(cx)
24504    });
24505
24506    assert_eq!(0, breakpoints.len());
24507    assert_breakpoint(&breakpoints, &abs_path, vec![]);
24508}
24509
24510#[gpui::test]
24511async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
24512    init_test(cx, |_| {});
24513
24514    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
24515
24516    let fs = FakeFs::new(cx.executor());
24517    fs.insert_tree(
24518        path!("/a"),
24519        json!({
24520            "main.rs": sample_text,
24521        }),
24522    )
24523    .await;
24524    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24525    let (workspace, cx) =
24526        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24527
24528    let worktree_id = workspace.update(cx, |workspace, cx| {
24529        workspace.project().update(cx, |project, cx| {
24530            project.worktrees(cx).next().unwrap().read(cx).id()
24531        })
24532    });
24533
24534    let buffer = project
24535        .update(cx, |project, cx| {
24536            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
24537        })
24538        .await
24539        .unwrap();
24540
24541    let (editor, cx) = cx.add_window_view(|window, cx| {
24542        Editor::new(
24543            EditorMode::full(),
24544            MultiBuffer::build_from_buffer(buffer, cx),
24545            Some(project.clone()),
24546            window,
24547            cx,
24548        )
24549    });
24550
24551    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
24552    let abs_path = project.read_with(cx, |project, cx| {
24553        project
24554            .absolute_path(&project_path, cx)
24555            .map(Arc::from)
24556            .unwrap()
24557    });
24558
24559    editor.update_in(cx, |editor, window, cx| {
24560        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
24561    });
24562
24563    let breakpoints = editor.update(cx, |editor, cx| {
24564        editor
24565            .breakpoint_store()
24566            .as_ref()
24567            .unwrap()
24568            .read(cx)
24569            .all_source_breakpoints(cx)
24570    });
24571
24572    assert_breakpoint(
24573        &breakpoints,
24574        &abs_path,
24575        vec![(0, Breakpoint::new_log("hello world"))],
24576    );
24577
24578    // Removing a log message from a log breakpoint should remove it
24579    editor.update_in(cx, |editor, window, cx| {
24580        add_log_breakpoint_at_cursor(editor, "", window, cx);
24581    });
24582
24583    let breakpoints = editor.update(cx, |editor, cx| {
24584        editor
24585            .breakpoint_store()
24586            .as_ref()
24587            .unwrap()
24588            .read(cx)
24589            .all_source_breakpoints(cx)
24590    });
24591
24592    assert_breakpoint(&breakpoints, &abs_path, vec![]);
24593
24594    editor.update_in(cx, |editor, window, cx| {
24595        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24596        editor.move_to_end(&MoveToEnd, window, cx);
24597        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24598        // Not adding a log message to a standard breakpoint shouldn't remove it
24599        add_log_breakpoint_at_cursor(editor, "", window, cx);
24600    });
24601
24602    let breakpoints = editor.update(cx, |editor, cx| {
24603        editor
24604            .breakpoint_store()
24605            .as_ref()
24606            .unwrap()
24607            .read(cx)
24608            .all_source_breakpoints(cx)
24609    });
24610
24611    assert_breakpoint(
24612        &breakpoints,
24613        &abs_path,
24614        vec![
24615            (0, Breakpoint::new_standard()),
24616            (3, Breakpoint::new_standard()),
24617        ],
24618    );
24619
24620    editor.update_in(cx, |editor, window, cx| {
24621        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
24622    });
24623
24624    let breakpoints = editor.update(cx, |editor, cx| {
24625        editor
24626            .breakpoint_store()
24627            .as_ref()
24628            .unwrap()
24629            .read(cx)
24630            .all_source_breakpoints(cx)
24631    });
24632
24633    assert_breakpoint(
24634        &breakpoints,
24635        &abs_path,
24636        vec![
24637            (0, Breakpoint::new_standard()),
24638            (3, Breakpoint::new_log("hello world")),
24639        ],
24640    );
24641
24642    editor.update_in(cx, |editor, window, cx| {
24643        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
24644    });
24645
24646    let breakpoints = editor.update(cx, |editor, cx| {
24647        editor
24648            .breakpoint_store()
24649            .as_ref()
24650            .unwrap()
24651            .read(cx)
24652            .all_source_breakpoints(cx)
24653    });
24654
24655    assert_breakpoint(
24656        &breakpoints,
24657        &abs_path,
24658        vec![
24659            (0, Breakpoint::new_standard()),
24660            (3, Breakpoint::new_log("hello Earth!!")),
24661        ],
24662    );
24663}
24664
24665/// This also tests that Editor::breakpoint_at_cursor_head is working properly
24666/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
24667/// or when breakpoints were placed out of order. This tests for a regression too
24668#[gpui::test]
24669async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
24670    init_test(cx, |_| {});
24671
24672    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
24673    let fs = FakeFs::new(cx.executor());
24674    fs.insert_tree(
24675        path!("/a"),
24676        json!({
24677            "main.rs": sample_text,
24678        }),
24679    )
24680    .await;
24681    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24682    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24683    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24684
24685    let fs = FakeFs::new(cx.executor());
24686    fs.insert_tree(
24687        path!("/a"),
24688        json!({
24689            "main.rs": sample_text,
24690        }),
24691    )
24692    .await;
24693    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24694    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24695    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24696    let worktree_id = workspace
24697        .update(cx, |workspace, _window, cx| {
24698            workspace.project().update(cx, |project, cx| {
24699                project.worktrees(cx).next().unwrap().read(cx).id()
24700            })
24701        })
24702        .unwrap();
24703
24704    let buffer = project
24705        .update(cx, |project, cx| {
24706            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
24707        })
24708        .await
24709        .unwrap();
24710
24711    let (editor, cx) = cx.add_window_view(|window, cx| {
24712        Editor::new(
24713            EditorMode::full(),
24714            MultiBuffer::build_from_buffer(buffer, cx),
24715            Some(project.clone()),
24716            window,
24717            cx,
24718        )
24719    });
24720
24721    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
24722    let abs_path = project.read_with(cx, |project, cx| {
24723        project
24724            .absolute_path(&project_path, cx)
24725            .map(Arc::from)
24726            .unwrap()
24727    });
24728
24729    // assert we can add breakpoint on the first line
24730    editor.update_in(cx, |editor, window, cx| {
24731        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24732        editor.move_to_end(&MoveToEnd, window, cx);
24733        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24734        editor.move_up(&MoveUp, window, cx);
24735        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24736    });
24737
24738    let breakpoints = editor.update(cx, |editor, cx| {
24739        editor
24740            .breakpoint_store()
24741            .as_ref()
24742            .unwrap()
24743            .read(cx)
24744            .all_source_breakpoints(cx)
24745    });
24746
24747    assert_eq!(1, breakpoints.len());
24748    assert_breakpoint(
24749        &breakpoints,
24750        &abs_path,
24751        vec![
24752            (0, Breakpoint::new_standard()),
24753            (2, Breakpoint::new_standard()),
24754            (3, Breakpoint::new_standard()),
24755        ],
24756    );
24757
24758    editor.update_in(cx, |editor, window, cx| {
24759        editor.move_to_beginning(&MoveToBeginning, window, cx);
24760        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
24761        editor.move_to_end(&MoveToEnd, window, cx);
24762        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
24763        // Disabling a breakpoint that doesn't exist should do nothing
24764        editor.move_up(&MoveUp, window, cx);
24765        editor.move_up(&MoveUp, window, cx);
24766        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
24767    });
24768
24769    let breakpoints = editor.update(cx, |editor, cx| {
24770        editor
24771            .breakpoint_store()
24772            .as_ref()
24773            .unwrap()
24774            .read(cx)
24775            .all_source_breakpoints(cx)
24776    });
24777
24778    let disable_breakpoint = {
24779        let mut bp = Breakpoint::new_standard();
24780        bp.state = BreakpointState::Disabled;
24781        bp
24782    };
24783
24784    assert_eq!(1, breakpoints.len());
24785    assert_breakpoint(
24786        &breakpoints,
24787        &abs_path,
24788        vec![
24789            (0, disable_breakpoint.clone()),
24790            (2, Breakpoint::new_standard()),
24791            (3, disable_breakpoint.clone()),
24792        ],
24793    );
24794
24795    editor.update_in(cx, |editor, window, cx| {
24796        editor.move_to_beginning(&MoveToBeginning, window, cx);
24797        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
24798        editor.move_to_end(&MoveToEnd, window, cx);
24799        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
24800        editor.move_up(&MoveUp, window, cx);
24801        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
24802    });
24803
24804    let breakpoints = editor.update(cx, |editor, cx| {
24805        editor
24806            .breakpoint_store()
24807            .as_ref()
24808            .unwrap()
24809            .read(cx)
24810            .all_source_breakpoints(cx)
24811    });
24812
24813    assert_eq!(1, breakpoints.len());
24814    assert_breakpoint(
24815        &breakpoints,
24816        &abs_path,
24817        vec![
24818            (0, Breakpoint::new_standard()),
24819            (2, disable_breakpoint),
24820            (3, Breakpoint::new_standard()),
24821        ],
24822    );
24823}
24824
24825#[gpui::test]
24826async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
24827    init_test(cx, |_| {});
24828    let capabilities = lsp::ServerCapabilities {
24829        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
24830            prepare_provider: Some(true),
24831            work_done_progress_options: Default::default(),
24832        })),
24833        ..Default::default()
24834    };
24835    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
24836
24837    cx.set_state(indoc! {"
24838        struct Fˇoo {}
24839    "});
24840
24841    cx.update_editor(|editor, _, cx| {
24842        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
24843        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
24844        editor.highlight_background::<DocumentHighlightRead>(
24845            &[highlight_range],
24846            |_, theme| theme.colors().editor_document_highlight_read_background,
24847            cx,
24848        );
24849    });
24850
24851    let mut prepare_rename_handler = cx
24852        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
24853            move |_, _, _| async move {
24854                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
24855                    start: lsp::Position {
24856                        line: 0,
24857                        character: 7,
24858                    },
24859                    end: lsp::Position {
24860                        line: 0,
24861                        character: 10,
24862                    },
24863                })))
24864            },
24865        );
24866    let prepare_rename_task = cx
24867        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
24868        .expect("Prepare rename was not started");
24869    prepare_rename_handler.next().await.unwrap();
24870    prepare_rename_task.await.expect("Prepare rename failed");
24871
24872    let mut rename_handler =
24873        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
24874            let edit = lsp::TextEdit {
24875                range: lsp::Range {
24876                    start: lsp::Position {
24877                        line: 0,
24878                        character: 7,
24879                    },
24880                    end: lsp::Position {
24881                        line: 0,
24882                        character: 10,
24883                    },
24884                },
24885                new_text: "FooRenamed".to_string(),
24886            };
24887            Ok(Some(lsp::WorkspaceEdit::new(
24888                // Specify the same edit twice
24889                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
24890            )))
24891        });
24892    let rename_task = cx
24893        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
24894        .expect("Confirm rename was not started");
24895    rename_handler.next().await.unwrap();
24896    rename_task.await.expect("Confirm rename failed");
24897    cx.run_until_parked();
24898
24899    // Despite two edits, only one is actually applied as those are identical
24900    cx.assert_editor_state(indoc! {"
24901        struct FooRenamedˇ {}
24902    "});
24903}
24904
24905#[gpui::test]
24906async fn test_rename_without_prepare(cx: &mut TestAppContext) {
24907    init_test(cx, |_| {});
24908    // These capabilities indicate that the server does not support prepare rename.
24909    let capabilities = lsp::ServerCapabilities {
24910        rename_provider: Some(lsp::OneOf::Left(true)),
24911        ..Default::default()
24912    };
24913    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
24914
24915    cx.set_state(indoc! {"
24916        struct Fˇoo {}
24917    "});
24918
24919    cx.update_editor(|editor, _window, cx| {
24920        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
24921        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
24922        editor.highlight_background::<DocumentHighlightRead>(
24923            &[highlight_range],
24924            |_, theme| theme.colors().editor_document_highlight_read_background,
24925            cx,
24926        );
24927    });
24928
24929    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
24930        .expect("Prepare rename was not started")
24931        .await
24932        .expect("Prepare rename failed");
24933
24934    let mut rename_handler =
24935        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
24936            let edit = lsp::TextEdit {
24937                range: lsp::Range {
24938                    start: lsp::Position {
24939                        line: 0,
24940                        character: 7,
24941                    },
24942                    end: lsp::Position {
24943                        line: 0,
24944                        character: 10,
24945                    },
24946                },
24947                new_text: "FooRenamed".to_string(),
24948            };
24949            Ok(Some(lsp::WorkspaceEdit::new(
24950                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
24951            )))
24952        });
24953    let rename_task = cx
24954        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
24955        .expect("Confirm rename was not started");
24956    rename_handler.next().await.unwrap();
24957    rename_task.await.expect("Confirm rename failed");
24958    cx.run_until_parked();
24959
24960    // Correct range is renamed, as `surrounding_word` is used to find it.
24961    cx.assert_editor_state(indoc! {"
24962        struct FooRenamedˇ {}
24963    "});
24964}
24965
24966#[gpui::test]
24967async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
24968    init_test(cx, |_| {});
24969    let mut cx = EditorTestContext::new(cx).await;
24970
24971    let language = Arc::new(
24972        Language::new(
24973            LanguageConfig::default(),
24974            Some(tree_sitter_html::LANGUAGE.into()),
24975        )
24976        .with_brackets_query(
24977            r#"
24978            ("<" @open "/>" @close)
24979            ("</" @open ">" @close)
24980            ("<" @open ">" @close)
24981            ("\"" @open "\"" @close)
24982            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
24983        "#,
24984        )
24985        .unwrap(),
24986    );
24987    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24988
24989    cx.set_state(indoc! {"
24990        <span>ˇ</span>
24991    "});
24992    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
24993    cx.assert_editor_state(indoc! {"
24994        <span>
24995        ˇ
24996        </span>
24997    "});
24998
24999    cx.set_state(indoc! {"
25000        <span><span></span>ˇ</span>
25001    "});
25002    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
25003    cx.assert_editor_state(indoc! {"
25004        <span><span></span>
25005        ˇ</span>
25006    "});
25007
25008    cx.set_state(indoc! {"
25009        <span>ˇ
25010        </span>
25011    "});
25012    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
25013    cx.assert_editor_state(indoc! {"
25014        <span>
25015        ˇ
25016        </span>
25017    "});
25018}
25019
25020#[gpui::test(iterations = 10)]
25021async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
25022    init_test(cx, |_| {});
25023
25024    let fs = FakeFs::new(cx.executor());
25025    fs.insert_tree(
25026        path!("/dir"),
25027        json!({
25028            "a.ts": "a",
25029        }),
25030    )
25031    .await;
25032
25033    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
25034    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25035    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
25036
25037    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25038    language_registry.add(Arc::new(Language::new(
25039        LanguageConfig {
25040            name: "TypeScript".into(),
25041            matcher: LanguageMatcher {
25042                path_suffixes: vec!["ts".to_string()],
25043                ..Default::default()
25044            },
25045            ..Default::default()
25046        },
25047        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
25048    )));
25049    let mut fake_language_servers = language_registry.register_fake_lsp(
25050        "TypeScript",
25051        FakeLspAdapter {
25052            capabilities: lsp::ServerCapabilities {
25053                code_lens_provider: Some(lsp::CodeLensOptions {
25054                    resolve_provider: Some(true),
25055                }),
25056                execute_command_provider: Some(lsp::ExecuteCommandOptions {
25057                    commands: vec!["_the/command".to_string()],
25058                    ..lsp::ExecuteCommandOptions::default()
25059                }),
25060                ..lsp::ServerCapabilities::default()
25061            },
25062            ..FakeLspAdapter::default()
25063        },
25064    );
25065
25066    let editor = workspace
25067        .update(cx, |workspace, window, cx| {
25068            workspace.open_abs_path(
25069                PathBuf::from(path!("/dir/a.ts")),
25070                OpenOptions::default(),
25071                window,
25072                cx,
25073            )
25074        })
25075        .unwrap()
25076        .await
25077        .unwrap()
25078        .downcast::<Editor>()
25079        .unwrap();
25080    cx.executor().run_until_parked();
25081
25082    let fake_server = fake_language_servers.next().await.unwrap();
25083
25084    let buffer = editor.update(cx, |editor, cx| {
25085        editor
25086            .buffer()
25087            .read(cx)
25088            .as_singleton()
25089            .expect("have opened a single file by path")
25090    });
25091
25092    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
25093    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
25094    drop(buffer_snapshot);
25095    let actions = cx
25096        .update_window(*workspace, |_, window, cx| {
25097            project.code_actions(&buffer, anchor..anchor, window, cx)
25098        })
25099        .unwrap();
25100
25101    fake_server
25102        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
25103            Ok(Some(vec![
25104                lsp::CodeLens {
25105                    range: lsp::Range::default(),
25106                    command: Some(lsp::Command {
25107                        title: "Code lens command".to_owned(),
25108                        command: "_the/command".to_owned(),
25109                        arguments: None,
25110                    }),
25111                    data: None,
25112                },
25113                lsp::CodeLens {
25114                    range: lsp::Range::default(),
25115                    command: Some(lsp::Command {
25116                        title: "Command not in capabilities".to_owned(),
25117                        command: "not in capabilities".to_owned(),
25118                        arguments: None,
25119                    }),
25120                    data: None,
25121                },
25122                lsp::CodeLens {
25123                    range: lsp::Range {
25124                        start: lsp::Position {
25125                            line: 1,
25126                            character: 1,
25127                        },
25128                        end: lsp::Position {
25129                            line: 1,
25130                            character: 1,
25131                        },
25132                    },
25133                    command: Some(lsp::Command {
25134                        title: "Command not in range".to_owned(),
25135                        command: "_the/command".to_owned(),
25136                        arguments: None,
25137                    }),
25138                    data: None,
25139                },
25140            ]))
25141        })
25142        .next()
25143        .await;
25144
25145    let actions = actions.await.unwrap();
25146    assert_eq!(
25147        actions.len(),
25148        1,
25149        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
25150    );
25151    let action = actions[0].clone();
25152    let apply = project.update(cx, |project, cx| {
25153        project.apply_code_action(buffer.clone(), action, true, cx)
25154    });
25155
25156    // Resolving the code action does not populate its edits. In absence of
25157    // edits, we must execute the given command.
25158    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
25159        |mut lens, _| async move {
25160            let lens_command = lens.command.as_mut().expect("should have a command");
25161            assert_eq!(lens_command.title, "Code lens command");
25162            lens_command.arguments = Some(vec![json!("the-argument")]);
25163            Ok(lens)
25164        },
25165    );
25166
25167    // While executing the command, the language server sends the editor
25168    // a `workspaceEdit` request.
25169    fake_server
25170        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
25171            let fake = fake_server.clone();
25172            move |params, _| {
25173                assert_eq!(params.command, "_the/command");
25174                let fake = fake.clone();
25175                async move {
25176                    fake.server
25177                        .request::<lsp::request::ApplyWorkspaceEdit>(
25178                            lsp::ApplyWorkspaceEditParams {
25179                                label: None,
25180                                edit: lsp::WorkspaceEdit {
25181                                    changes: Some(
25182                                        [(
25183                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
25184                                            vec![lsp::TextEdit {
25185                                                range: lsp::Range::new(
25186                                                    lsp::Position::new(0, 0),
25187                                                    lsp::Position::new(0, 0),
25188                                                ),
25189                                                new_text: "X".into(),
25190                                            }],
25191                                        )]
25192                                        .into_iter()
25193                                        .collect(),
25194                                    ),
25195                                    ..lsp::WorkspaceEdit::default()
25196                                },
25197                            },
25198                        )
25199                        .await
25200                        .into_response()
25201                        .unwrap();
25202                    Ok(Some(json!(null)))
25203                }
25204            }
25205        })
25206        .next()
25207        .await;
25208
25209    // Applying the code lens command returns a project transaction containing the edits
25210    // sent by the language server in its `workspaceEdit` request.
25211    let transaction = apply.await.unwrap();
25212    assert!(transaction.0.contains_key(&buffer));
25213    buffer.update(cx, |buffer, cx| {
25214        assert_eq!(buffer.text(), "Xa");
25215        buffer.undo(cx);
25216        assert_eq!(buffer.text(), "a");
25217    });
25218
25219    let actions_after_edits = cx
25220        .update_window(*workspace, |_, window, cx| {
25221            project.code_actions(&buffer, anchor..anchor, window, cx)
25222        })
25223        .unwrap()
25224        .await
25225        .unwrap();
25226    assert_eq!(
25227        actions, actions_after_edits,
25228        "For the same selection, same code lens actions should be returned"
25229    );
25230
25231    let _responses =
25232        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
25233            panic!("No more code lens requests are expected");
25234        });
25235    editor.update_in(cx, |editor, window, cx| {
25236        editor.select_all(&SelectAll, window, cx);
25237    });
25238    cx.executor().run_until_parked();
25239    let new_actions = cx
25240        .update_window(*workspace, |_, window, cx| {
25241            project.code_actions(&buffer, anchor..anchor, window, cx)
25242        })
25243        .unwrap()
25244        .await
25245        .unwrap();
25246    assert_eq!(
25247        actions, new_actions,
25248        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
25249    );
25250}
25251
25252#[gpui::test]
25253async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
25254    init_test(cx, |_| {});
25255
25256    let fs = FakeFs::new(cx.executor());
25257    let main_text = r#"fn main() {
25258println!("1");
25259println!("2");
25260println!("3");
25261println!("4");
25262println!("5");
25263}"#;
25264    let lib_text = "mod foo {}";
25265    fs.insert_tree(
25266        path!("/a"),
25267        json!({
25268            "lib.rs": lib_text,
25269            "main.rs": main_text,
25270        }),
25271    )
25272    .await;
25273
25274    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25275    let (workspace, cx) =
25276        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
25277    let worktree_id = workspace.update(cx, |workspace, cx| {
25278        workspace.project().update(cx, |project, cx| {
25279            project.worktrees(cx).next().unwrap().read(cx).id()
25280        })
25281    });
25282
25283    let expected_ranges = vec![
25284        Point::new(0, 0)..Point::new(0, 0),
25285        Point::new(1, 0)..Point::new(1, 1),
25286        Point::new(2, 0)..Point::new(2, 2),
25287        Point::new(3, 0)..Point::new(3, 3),
25288    ];
25289
25290    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
25291    let editor_1 = workspace
25292        .update_in(cx, |workspace, window, cx| {
25293            workspace.open_path(
25294                (worktree_id, rel_path("main.rs")),
25295                Some(pane_1.downgrade()),
25296                true,
25297                window,
25298                cx,
25299            )
25300        })
25301        .unwrap()
25302        .await
25303        .downcast::<Editor>()
25304        .unwrap();
25305    pane_1.update(cx, |pane, cx| {
25306        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25307        open_editor.update(cx, |editor, cx| {
25308            assert_eq!(
25309                editor.display_text(cx),
25310                main_text,
25311                "Original main.rs text on initial open",
25312            );
25313            assert_eq!(
25314                editor
25315                    .selections
25316                    .all::<Point>(&editor.display_snapshot(cx))
25317                    .into_iter()
25318                    .map(|s| s.range())
25319                    .collect::<Vec<_>>(),
25320                vec![Point::zero()..Point::zero()],
25321                "Default selections on initial open",
25322            );
25323        })
25324    });
25325    editor_1.update_in(cx, |editor, window, cx| {
25326        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25327            s.select_ranges(expected_ranges.clone());
25328        });
25329    });
25330
25331    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
25332        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
25333    });
25334    let editor_2 = workspace
25335        .update_in(cx, |workspace, window, cx| {
25336            workspace.open_path(
25337                (worktree_id, rel_path("main.rs")),
25338                Some(pane_2.downgrade()),
25339                true,
25340                window,
25341                cx,
25342            )
25343        })
25344        .unwrap()
25345        .await
25346        .downcast::<Editor>()
25347        .unwrap();
25348    pane_2.update(cx, |pane, cx| {
25349        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25350        open_editor.update(cx, |editor, cx| {
25351            assert_eq!(
25352                editor.display_text(cx),
25353                main_text,
25354                "Original main.rs text on initial open in another panel",
25355            );
25356            assert_eq!(
25357                editor
25358                    .selections
25359                    .all::<Point>(&editor.display_snapshot(cx))
25360                    .into_iter()
25361                    .map(|s| s.range())
25362                    .collect::<Vec<_>>(),
25363                vec![Point::zero()..Point::zero()],
25364                "Default selections on initial open in another panel",
25365            );
25366        })
25367    });
25368
25369    editor_2.update_in(cx, |editor, window, cx| {
25370        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
25371    });
25372
25373    let _other_editor_1 = workspace
25374        .update_in(cx, |workspace, window, cx| {
25375            workspace.open_path(
25376                (worktree_id, rel_path("lib.rs")),
25377                Some(pane_1.downgrade()),
25378                true,
25379                window,
25380                cx,
25381            )
25382        })
25383        .unwrap()
25384        .await
25385        .downcast::<Editor>()
25386        .unwrap();
25387    pane_1
25388        .update_in(cx, |pane, window, cx| {
25389            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
25390        })
25391        .await
25392        .unwrap();
25393    drop(editor_1);
25394    pane_1.update(cx, |pane, cx| {
25395        pane.active_item()
25396            .unwrap()
25397            .downcast::<Editor>()
25398            .unwrap()
25399            .update(cx, |editor, cx| {
25400                assert_eq!(
25401                    editor.display_text(cx),
25402                    lib_text,
25403                    "Other file should be open and active",
25404                );
25405            });
25406        assert_eq!(pane.items().count(), 1, "No other editors should be open");
25407    });
25408
25409    let _other_editor_2 = workspace
25410        .update_in(cx, |workspace, window, cx| {
25411            workspace.open_path(
25412                (worktree_id, rel_path("lib.rs")),
25413                Some(pane_2.downgrade()),
25414                true,
25415                window,
25416                cx,
25417            )
25418        })
25419        .unwrap()
25420        .await
25421        .downcast::<Editor>()
25422        .unwrap();
25423    pane_2
25424        .update_in(cx, |pane, window, cx| {
25425            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
25426        })
25427        .await
25428        .unwrap();
25429    drop(editor_2);
25430    pane_2.update(cx, |pane, cx| {
25431        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25432        open_editor.update(cx, |editor, cx| {
25433            assert_eq!(
25434                editor.display_text(cx),
25435                lib_text,
25436                "Other file should be open and active in another panel too",
25437            );
25438        });
25439        assert_eq!(
25440            pane.items().count(),
25441            1,
25442            "No other editors should be open in another pane",
25443        );
25444    });
25445
25446    let _editor_1_reopened = workspace
25447        .update_in(cx, |workspace, window, cx| {
25448            workspace.open_path(
25449                (worktree_id, rel_path("main.rs")),
25450                Some(pane_1.downgrade()),
25451                true,
25452                window,
25453                cx,
25454            )
25455        })
25456        .unwrap()
25457        .await
25458        .downcast::<Editor>()
25459        .unwrap();
25460    let _editor_2_reopened = workspace
25461        .update_in(cx, |workspace, window, cx| {
25462            workspace.open_path(
25463                (worktree_id, rel_path("main.rs")),
25464                Some(pane_2.downgrade()),
25465                true,
25466                window,
25467                cx,
25468            )
25469        })
25470        .unwrap()
25471        .await
25472        .downcast::<Editor>()
25473        .unwrap();
25474    pane_1.update(cx, |pane, cx| {
25475        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25476        open_editor.update(cx, |editor, cx| {
25477            assert_eq!(
25478                editor.display_text(cx),
25479                main_text,
25480                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
25481            );
25482            assert_eq!(
25483                editor
25484                    .selections
25485                    .all::<Point>(&editor.display_snapshot(cx))
25486                    .into_iter()
25487                    .map(|s| s.range())
25488                    .collect::<Vec<_>>(),
25489                expected_ranges,
25490                "Previous editor in the 1st panel had selections and should get them restored on reopen",
25491            );
25492        })
25493    });
25494    pane_2.update(cx, |pane, cx| {
25495        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25496        open_editor.update(cx, |editor, cx| {
25497            assert_eq!(
25498                editor.display_text(cx),
25499                r#"fn main() {
25500⋯rintln!("1");
25501⋯intln!("2");
25502⋯ntln!("3");
25503println!("4");
25504println!("5");
25505}"#,
25506                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
25507            );
25508            assert_eq!(
25509                editor
25510                    .selections
25511                    .all::<Point>(&editor.display_snapshot(cx))
25512                    .into_iter()
25513                    .map(|s| s.range())
25514                    .collect::<Vec<_>>(),
25515                vec![Point::zero()..Point::zero()],
25516                "Previous editor in the 2nd pane had no selections changed hence should restore none",
25517            );
25518        })
25519    });
25520}
25521
25522#[gpui::test]
25523async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
25524    init_test(cx, |_| {});
25525
25526    let fs = FakeFs::new(cx.executor());
25527    let main_text = r#"fn main() {
25528println!("1");
25529println!("2");
25530println!("3");
25531println!("4");
25532println!("5");
25533}"#;
25534    let lib_text = "mod foo {}";
25535    fs.insert_tree(
25536        path!("/a"),
25537        json!({
25538            "lib.rs": lib_text,
25539            "main.rs": main_text,
25540        }),
25541    )
25542    .await;
25543
25544    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25545    let (workspace, cx) =
25546        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
25547    let worktree_id = workspace.update(cx, |workspace, cx| {
25548        workspace.project().update(cx, |project, cx| {
25549            project.worktrees(cx).next().unwrap().read(cx).id()
25550        })
25551    });
25552
25553    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
25554    let editor = workspace
25555        .update_in(cx, |workspace, window, cx| {
25556            workspace.open_path(
25557                (worktree_id, rel_path("main.rs")),
25558                Some(pane.downgrade()),
25559                true,
25560                window,
25561                cx,
25562            )
25563        })
25564        .unwrap()
25565        .await
25566        .downcast::<Editor>()
25567        .unwrap();
25568    pane.update(cx, |pane, cx| {
25569        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25570        open_editor.update(cx, |editor, cx| {
25571            assert_eq!(
25572                editor.display_text(cx),
25573                main_text,
25574                "Original main.rs text on initial open",
25575            );
25576        })
25577    });
25578    editor.update_in(cx, |editor, window, cx| {
25579        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
25580    });
25581
25582    cx.update_global(|store: &mut SettingsStore, cx| {
25583        store.update_user_settings(cx, |s| {
25584            s.workspace.restore_on_file_reopen = Some(false);
25585        });
25586    });
25587    editor.update_in(cx, |editor, window, cx| {
25588        editor.fold_ranges(
25589            vec![
25590                Point::new(1, 0)..Point::new(1, 1),
25591                Point::new(2, 0)..Point::new(2, 2),
25592                Point::new(3, 0)..Point::new(3, 3),
25593            ],
25594            false,
25595            window,
25596            cx,
25597        );
25598    });
25599    pane.update_in(cx, |pane, window, cx| {
25600        pane.close_all_items(&CloseAllItems::default(), window, cx)
25601    })
25602    .await
25603    .unwrap();
25604    pane.update(cx, |pane, _| {
25605        assert!(pane.active_item().is_none());
25606    });
25607    cx.update_global(|store: &mut SettingsStore, cx| {
25608        store.update_user_settings(cx, |s| {
25609            s.workspace.restore_on_file_reopen = Some(true);
25610        });
25611    });
25612
25613    let _editor_reopened = workspace
25614        .update_in(cx, |workspace, window, cx| {
25615            workspace.open_path(
25616                (worktree_id, rel_path("main.rs")),
25617                Some(pane.downgrade()),
25618                true,
25619                window,
25620                cx,
25621            )
25622        })
25623        .unwrap()
25624        .await
25625        .downcast::<Editor>()
25626        .unwrap();
25627    pane.update(cx, |pane, cx| {
25628        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25629        open_editor.update(cx, |editor, cx| {
25630            assert_eq!(
25631                editor.display_text(cx),
25632                main_text,
25633                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
25634            );
25635        })
25636    });
25637}
25638
25639#[gpui::test]
25640async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
25641    struct EmptyModalView {
25642        focus_handle: gpui::FocusHandle,
25643    }
25644    impl EventEmitter<DismissEvent> for EmptyModalView {}
25645    impl Render for EmptyModalView {
25646        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
25647            div()
25648        }
25649    }
25650    impl Focusable for EmptyModalView {
25651        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
25652            self.focus_handle.clone()
25653        }
25654    }
25655    impl workspace::ModalView for EmptyModalView {}
25656    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
25657        EmptyModalView {
25658            focus_handle: cx.focus_handle(),
25659        }
25660    }
25661
25662    init_test(cx, |_| {});
25663
25664    let fs = FakeFs::new(cx.executor());
25665    let project = Project::test(fs, [], cx).await;
25666    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25667    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
25668    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
25669    let editor = cx.new_window_entity(|window, cx| {
25670        Editor::new(
25671            EditorMode::full(),
25672            buffer,
25673            Some(project.clone()),
25674            window,
25675            cx,
25676        )
25677    });
25678    workspace
25679        .update(cx, |workspace, window, cx| {
25680            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
25681        })
25682        .unwrap();
25683    editor.update_in(cx, |editor, window, cx| {
25684        editor.open_context_menu(&OpenContextMenu, window, cx);
25685        assert!(editor.mouse_context_menu.is_some());
25686    });
25687    workspace
25688        .update(cx, |workspace, window, cx| {
25689            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
25690        })
25691        .unwrap();
25692    cx.read(|cx| {
25693        assert!(editor.read(cx).mouse_context_menu.is_none());
25694    });
25695}
25696
25697fn set_linked_edit_ranges(
25698    opening: (Point, Point),
25699    closing: (Point, Point),
25700    editor: &mut Editor,
25701    cx: &mut Context<Editor>,
25702) {
25703    let Some((buffer, _)) = editor
25704        .buffer
25705        .read(cx)
25706        .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
25707    else {
25708        panic!("Failed to get buffer for selection position");
25709    };
25710    let buffer = buffer.read(cx);
25711    let buffer_id = buffer.remote_id();
25712    let opening_range = buffer.anchor_before(opening.0)..buffer.anchor_after(opening.1);
25713    let closing_range = buffer.anchor_before(closing.0)..buffer.anchor_after(closing.1);
25714    let mut linked_ranges = HashMap::default();
25715    linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
25716    editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
25717}
25718
25719#[gpui::test]
25720async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
25721    init_test(cx, |_| {});
25722
25723    let fs = FakeFs::new(cx.executor());
25724    fs.insert_file(path!("/file.html"), Default::default())
25725        .await;
25726
25727    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
25728
25729    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25730    let html_language = Arc::new(Language::new(
25731        LanguageConfig {
25732            name: "HTML".into(),
25733            matcher: LanguageMatcher {
25734                path_suffixes: vec!["html".to_string()],
25735                ..LanguageMatcher::default()
25736            },
25737            brackets: BracketPairConfig {
25738                pairs: vec![BracketPair {
25739                    start: "<".into(),
25740                    end: ">".into(),
25741                    close: true,
25742                    ..Default::default()
25743                }],
25744                ..Default::default()
25745            },
25746            ..Default::default()
25747        },
25748        Some(tree_sitter_html::LANGUAGE.into()),
25749    ));
25750    language_registry.add(html_language);
25751    let mut fake_servers = language_registry.register_fake_lsp(
25752        "HTML",
25753        FakeLspAdapter {
25754            capabilities: lsp::ServerCapabilities {
25755                completion_provider: Some(lsp::CompletionOptions {
25756                    resolve_provider: Some(true),
25757                    ..Default::default()
25758                }),
25759                ..Default::default()
25760            },
25761            ..Default::default()
25762        },
25763    );
25764
25765    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25766    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25767
25768    let worktree_id = workspace
25769        .update(cx, |workspace, _window, cx| {
25770            workspace.project().update(cx, |project, cx| {
25771                project.worktrees(cx).next().unwrap().read(cx).id()
25772            })
25773        })
25774        .unwrap();
25775    project
25776        .update(cx, |project, cx| {
25777            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
25778        })
25779        .await
25780        .unwrap();
25781    let editor = workspace
25782        .update(cx, |workspace, window, cx| {
25783            workspace.open_path((worktree_id, rel_path("file.html")), None, true, window, cx)
25784        })
25785        .unwrap()
25786        .await
25787        .unwrap()
25788        .downcast::<Editor>()
25789        .unwrap();
25790
25791    let fake_server = fake_servers.next().await.unwrap();
25792    cx.run_until_parked();
25793    editor.update_in(cx, |editor, window, cx| {
25794        editor.set_text("<ad></ad>", window, cx);
25795        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
25796            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
25797        });
25798        set_linked_edit_ranges(
25799            (Point::new(0, 1), Point::new(0, 3)),
25800            (Point::new(0, 6), Point::new(0, 8)),
25801            editor,
25802            cx,
25803        );
25804    });
25805    let mut completion_handle =
25806        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
25807            Ok(Some(lsp::CompletionResponse::Array(vec![
25808                lsp::CompletionItem {
25809                    label: "head".to_string(),
25810                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
25811                        lsp::InsertReplaceEdit {
25812                            new_text: "head".to_string(),
25813                            insert: lsp::Range::new(
25814                                lsp::Position::new(0, 1),
25815                                lsp::Position::new(0, 3),
25816                            ),
25817                            replace: lsp::Range::new(
25818                                lsp::Position::new(0, 1),
25819                                lsp::Position::new(0, 3),
25820                            ),
25821                        },
25822                    )),
25823                    ..Default::default()
25824                },
25825            ])))
25826        });
25827    editor.update_in(cx, |editor, window, cx| {
25828        editor.show_completions(&ShowCompletions, window, cx);
25829    });
25830    cx.run_until_parked();
25831    completion_handle.next().await.unwrap();
25832    editor.update(cx, |editor, _| {
25833        assert!(
25834            editor.context_menu_visible(),
25835            "Completion menu should be visible"
25836        );
25837    });
25838    editor.update_in(cx, |editor, window, cx| {
25839        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
25840    });
25841    cx.executor().run_until_parked();
25842    editor.update(cx, |editor, cx| {
25843        assert_eq!(editor.text(cx), "<head></head>");
25844    });
25845}
25846
25847#[gpui::test]
25848async fn test_linked_edits_on_typing_punctuation(cx: &mut TestAppContext) {
25849    init_test(cx, |_| {});
25850
25851    let mut cx = EditorTestContext::new(cx).await;
25852    let language = Arc::new(Language::new(
25853        LanguageConfig {
25854            name: "TSX".into(),
25855            matcher: LanguageMatcher {
25856                path_suffixes: vec!["tsx".to_string()],
25857                ..LanguageMatcher::default()
25858            },
25859            brackets: BracketPairConfig {
25860                pairs: vec![BracketPair {
25861                    start: "<".into(),
25862                    end: ">".into(),
25863                    close: true,
25864                    ..Default::default()
25865                }],
25866                ..Default::default()
25867            },
25868            linked_edit_characters: HashSet::from_iter(['.']),
25869            ..Default::default()
25870        },
25871        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
25872    ));
25873    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25874
25875    // Test typing > does not extend linked pair
25876    cx.set_state("<divˇ<div></div>");
25877    cx.update_editor(|editor, _, cx| {
25878        set_linked_edit_ranges(
25879            (Point::new(0, 1), Point::new(0, 4)),
25880            (Point::new(0, 11), Point::new(0, 14)),
25881            editor,
25882            cx,
25883        );
25884    });
25885    cx.update_editor(|editor, window, cx| {
25886        editor.handle_input(">", window, cx);
25887    });
25888    cx.assert_editor_state("<div>ˇ<div></div>");
25889
25890    // Test typing . do extend linked pair
25891    cx.set_state("<Animatedˇ></Animated>");
25892    cx.update_editor(|editor, _, cx| {
25893        set_linked_edit_ranges(
25894            (Point::new(0, 1), Point::new(0, 9)),
25895            (Point::new(0, 12), Point::new(0, 20)),
25896            editor,
25897            cx,
25898        );
25899    });
25900    cx.update_editor(|editor, window, cx| {
25901        editor.handle_input(".", window, cx);
25902    });
25903    cx.assert_editor_state("<Animated.ˇ></Animated.>");
25904    cx.update_editor(|editor, _, cx| {
25905        set_linked_edit_ranges(
25906            (Point::new(0, 1), Point::new(0, 10)),
25907            (Point::new(0, 13), Point::new(0, 21)),
25908            editor,
25909            cx,
25910        );
25911    });
25912    cx.update_editor(|editor, window, cx| {
25913        editor.handle_input("V", window, cx);
25914    });
25915    cx.assert_editor_state("<Animated.Vˇ></Animated.V>");
25916}
25917
25918#[gpui::test]
25919async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
25920    init_test(cx, |_| {});
25921
25922    let fs = FakeFs::new(cx.executor());
25923    fs.insert_tree(
25924        path!("/root"),
25925        json!({
25926            "a": {
25927                "main.rs": "fn main() {}",
25928            },
25929            "foo": {
25930                "bar": {
25931                    "external_file.rs": "pub mod external {}",
25932                }
25933            }
25934        }),
25935    )
25936    .await;
25937
25938    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
25939    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25940    language_registry.add(rust_lang());
25941    let _fake_servers = language_registry.register_fake_lsp(
25942        "Rust",
25943        FakeLspAdapter {
25944            ..FakeLspAdapter::default()
25945        },
25946    );
25947    let (workspace, cx) =
25948        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
25949    let worktree_id = workspace.update(cx, |workspace, cx| {
25950        workspace.project().update(cx, |project, cx| {
25951            project.worktrees(cx).next().unwrap().read(cx).id()
25952        })
25953    });
25954
25955    let assert_language_servers_count =
25956        |expected: usize, context: &str, cx: &mut VisualTestContext| {
25957            project.update(cx, |project, cx| {
25958                let current = project
25959                    .lsp_store()
25960                    .read(cx)
25961                    .as_local()
25962                    .unwrap()
25963                    .language_servers
25964                    .len();
25965                assert_eq!(expected, current, "{context}");
25966            });
25967        };
25968
25969    assert_language_servers_count(
25970        0,
25971        "No servers should be running before any file is open",
25972        cx,
25973    );
25974    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
25975    let main_editor = workspace
25976        .update_in(cx, |workspace, window, cx| {
25977            workspace.open_path(
25978                (worktree_id, rel_path("main.rs")),
25979                Some(pane.downgrade()),
25980                true,
25981                window,
25982                cx,
25983            )
25984        })
25985        .unwrap()
25986        .await
25987        .downcast::<Editor>()
25988        .unwrap();
25989    pane.update(cx, |pane, cx| {
25990        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25991        open_editor.update(cx, |editor, cx| {
25992            assert_eq!(
25993                editor.display_text(cx),
25994                "fn main() {}",
25995                "Original main.rs text on initial open",
25996            );
25997        });
25998        assert_eq!(open_editor, main_editor);
25999    });
26000    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
26001
26002    let external_editor = workspace
26003        .update_in(cx, |workspace, window, cx| {
26004            workspace.open_abs_path(
26005                PathBuf::from("/root/foo/bar/external_file.rs"),
26006                OpenOptions::default(),
26007                window,
26008                cx,
26009            )
26010        })
26011        .await
26012        .expect("opening external file")
26013        .downcast::<Editor>()
26014        .expect("downcasted external file's open element to editor");
26015    pane.update(cx, |pane, cx| {
26016        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
26017        open_editor.update(cx, |editor, cx| {
26018            assert_eq!(
26019                editor.display_text(cx),
26020                "pub mod external {}",
26021                "External file is open now",
26022            );
26023        });
26024        assert_eq!(open_editor, external_editor);
26025    });
26026    assert_language_servers_count(
26027        1,
26028        "Second, external, *.rs file should join the existing server",
26029        cx,
26030    );
26031
26032    pane.update_in(cx, |pane, window, cx| {
26033        pane.close_active_item(&CloseActiveItem::default(), window, cx)
26034    })
26035    .await
26036    .unwrap();
26037    pane.update_in(cx, |pane, window, cx| {
26038        pane.navigate_backward(&Default::default(), window, cx);
26039    });
26040    cx.run_until_parked();
26041    pane.update(cx, |pane, cx| {
26042        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
26043        open_editor.update(cx, |editor, cx| {
26044            assert_eq!(
26045                editor.display_text(cx),
26046                "pub mod external {}",
26047                "External file is open now",
26048            );
26049        });
26050    });
26051    assert_language_servers_count(
26052        1,
26053        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
26054        cx,
26055    );
26056
26057    cx.update(|_, cx| {
26058        workspace::reload(cx);
26059    });
26060    assert_language_servers_count(
26061        1,
26062        "After reloading the worktree with local and external files opened, only one project should be started",
26063        cx,
26064    );
26065}
26066
26067#[gpui::test]
26068async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
26069    init_test(cx, |_| {});
26070
26071    let mut cx = EditorTestContext::new(cx).await;
26072    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
26073    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26074
26075    // test cursor move to start of each line on tab
26076    // for `if`, `elif`, `else`, `while`, `with` and `for`
26077    cx.set_state(indoc! {"
26078        def main():
26079        ˇ    for item in items:
26080        ˇ        while item.active:
26081        ˇ            if item.value > 10:
26082        ˇ                continue
26083        ˇ            elif item.value < 0:
26084        ˇ                break
26085        ˇ            else:
26086        ˇ                with item.context() as ctx:
26087        ˇ                    yield count
26088        ˇ        else:
26089        ˇ            log('while else')
26090        ˇ    else:
26091        ˇ        log('for else')
26092    "});
26093    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
26094    cx.wait_for_autoindent_applied().await;
26095    cx.assert_editor_state(indoc! {"
26096        def main():
26097            ˇfor item in items:
26098                ˇwhile item.active:
26099                    ˇif item.value > 10:
26100                        ˇcontinue
26101                    ˇelif item.value < 0:
26102                        ˇbreak
26103                    ˇelse:
26104                        ˇwith item.context() as ctx:
26105                            ˇyield count
26106                ˇelse:
26107                    ˇlog('while else')
26108            ˇelse:
26109                ˇlog('for else')
26110    "});
26111    // test relative indent is preserved when tab
26112    // for `if`, `elif`, `else`, `while`, `with` and `for`
26113    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
26114    cx.wait_for_autoindent_applied().await;
26115    cx.assert_editor_state(indoc! {"
26116        def main():
26117                ˇfor item in items:
26118                    ˇwhile item.active:
26119                        ˇif item.value > 10:
26120                            ˇcontinue
26121                        ˇelif item.value < 0:
26122                            ˇbreak
26123                        ˇelse:
26124                            ˇwith item.context() as ctx:
26125                                ˇyield count
26126                    ˇelse:
26127                        ˇlog('while else')
26128                ˇelse:
26129                    ˇlog('for else')
26130    "});
26131
26132    // test cursor move to start of each line on tab
26133    // for `try`, `except`, `else`, `finally`, `match` and `def`
26134    cx.set_state(indoc! {"
26135        def main():
26136        ˇ    try:
26137        ˇ        fetch()
26138        ˇ    except ValueError:
26139        ˇ        handle_error()
26140        ˇ    else:
26141        ˇ        match value:
26142        ˇ            case _:
26143        ˇ    finally:
26144        ˇ        def status():
26145        ˇ            return 0
26146    "});
26147    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
26148    cx.wait_for_autoindent_applied().await;
26149    cx.assert_editor_state(indoc! {"
26150        def main():
26151            ˇtry:
26152                ˇfetch()
26153            ˇexcept ValueError:
26154                ˇhandle_error()
26155            ˇelse:
26156                ˇmatch value:
26157                    ˇcase _:
26158            ˇfinally:
26159                ˇdef status():
26160                    ˇreturn 0
26161    "});
26162    // test relative indent is preserved when tab
26163    // for `try`, `except`, `else`, `finally`, `match` and `def`
26164    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
26165    cx.wait_for_autoindent_applied().await;
26166    cx.assert_editor_state(indoc! {"
26167        def main():
26168                ˇtry:
26169                    ˇfetch()
26170                ˇexcept ValueError:
26171                    ˇhandle_error()
26172                ˇelse:
26173                    ˇmatch value:
26174                        ˇcase _:
26175                ˇfinally:
26176                    ˇdef status():
26177                        ˇreturn 0
26178    "});
26179}
26180
26181#[gpui::test]
26182async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
26183    init_test(cx, |_| {});
26184
26185    let mut cx = EditorTestContext::new(cx).await;
26186    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
26187    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26188
26189    // test `else` auto outdents when typed inside `if` block
26190    cx.set_state(indoc! {"
26191        def main():
26192            if i == 2:
26193                return
26194                ˇ
26195    "});
26196    cx.update_editor(|editor, window, cx| {
26197        editor.handle_input("else:", window, cx);
26198    });
26199    cx.wait_for_autoindent_applied().await;
26200    cx.assert_editor_state(indoc! {"
26201        def main():
26202            if i == 2:
26203                return
26204            else:ˇ
26205    "});
26206
26207    // test `except` auto outdents when typed inside `try` block
26208    cx.set_state(indoc! {"
26209        def main():
26210            try:
26211                i = 2
26212                ˇ
26213    "});
26214    cx.update_editor(|editor, window, cx| {
26215        editor.handle_input("except:", window, cx);
26216    });
26217    cx.wait_for_autoindent_applied().await;
26218    cx.assert_editor_state(indoc! {"
26219        def main():
26220            try:
26221                i = 2
26222            except:ˇ
26223    "});
26224
26225    // test `else` auto outdents when typed inside `except` block
26226    cx.set_state(indoc! {"
26227        def main():
26228            try:
26229                i = 2
26230            except:
26231                j = 2
26232                ˇ
26233    "});
26234    cx.update_editor(|editor, window, cx| {
26235        editor.handle_input("else:", window, cx);
26236    });
26237    cx.wait_for_autoindent_applied().await;
26238    cx.assert_editor_state(indoc! {"
26239        def main():
26240            try:
26241                i = 2
26242            except:
26243                j = 2
26244            else:ˇ
26245    "});
26246
26247    // test `finally` auto outdents when typed inside `else` block
26248    cx.set_state(indoc! {"
26249        def main():
26250            try:
26251                i = 2
26252            except:
26253                j = 2
26254            else:
26255                k = 2
26256                ˇ
26257    "});
26258    cx.update_editor(|editor, window, cx| {
26259        editor.handle_input("finally:", window, cx);
26260    });
26261    cx.wait_for_autoindent_applied().await;
26262    cx.assert_editor_state(indoc! {"
26263        def main():
26264            try:
26265                i = 2
26266            except:
26267                j = 2
26268            else:
26269                k = 2
26270            finally:ˇ
26271    "});
26272
26273    // test `else` does not outdents when typed inside `except` block right after for block
26274    cx.set_state(indoc! {"
26275        def main():
26276            try:
26277                i = 2
26278            except:
26279                for i in range(n):
26280                    pass
26281                ˇ
26282    "});
26283    cx.update_editor(|editor, window, cx| {
26284        editor.handle_input("else:", window, cx);
26285    });
26286    cx.wait_for_autoindent_applied().await;
26287    cx.assert_editor_state(indoc! {"
26288        def main():
26289            try:
26290                i = 2
26291            except:
26292                for i in range(n):
26293                    pass
26294                else:ˇ
26295    "});
26296
26297    // test `finally` auto outdents when typed inside `else` block right after for block
26298    cx.set_state(indoc! {"
26299        def main():
26300            try:
26301                i = 2
26302            except:
26303                j = 2
26304            else:
26305                for i in range(n):
26306                    pass
26307                ˇ
26308    "});
26309    cx.update_editor(|editor, window, cx| {
26310        editor.handle_input("finally:", window, cx);
26311    });
26312    cx.wait_for_autoindent_applied().await;
26313    cx.assert_editor_state(indoc! {"
26314        def main():
26315            try:
26316                i = 2
26317            except:
26318                j = 2
26319            else:
26320                for i in range(n):
26321                    pass
26322            finally:ˇ
26323    "});
26324
26325    // test `except` outdents to inner "try" block
26326    cx.set_state(indoc! {"
26327        def main():
26328            try:
26329                i = 2
26330                if i == 2:
26331                    try:
26332                        i = 3
26333                        ˇ
26334    "});
26335    cx.update_editor(|editor, window, cx| {
26336        editor.handle_input("except:", window, cx);
26337    });
26338    cx.wait_for_autoindent_applied().await;
26339    cx.assert_editor_state(indoc! {"
26340        def main():
26341            try:
26342                i = 2
26343                if i == 2:
26344                    try:
26345                        i = 3
26346                    except:ˇ
26347    "});
26348
26349    // test `except` outdents to outer "try" block
26350    cx.set_state(indoc! {"
26351        def main():
26352            try:
26353                i = 2
26354                if i == 2:
26355                    try:
26356                        i = 3
26357                ˇ
26358    "});
26359    cx.update_editor(|editor, window, cx| {
26360        editor.handle_input("except:", window, cx);
26361    });
26362    cx.wait_for_autoindent_applied().await;
26363    cx.assert_editor_state(indoc! {"
26364        def main():
26365            try:
26366                i = 2
26367                if i == 2:
26368                    try:
26369                        i = 3
26370            except:ˇ
26371    "});
26372
26373    // test `else` stays at correct indent when typed after `for` block
26374    cx.set_state(indoc! {"
26375        def main():
26376            for i in range(10):
26377                if i == 3:
26378                    break
26379            ˇ
26380    "});
26381    cx.update_editor(|editor, window, cx| {
26382        editor.handle_input("else:", window, cx);
26383    });
26384    cx.wait_for_autoindent_applied().await;
26385    cx.assert_editor_state(indoc! {"
26386        def main():
26387            for i in range(10):
26388                if i == 3:
26389                    break
26390            else:ˇ
26391    "});
26392
26393    // test does not outdent on typing after line with square brackets
26394    cx.set_state(indoc! {"
26395        def f() -> list[str]:
26396            ˇ
26397    "});
26398    cx.update_editor(|editor, window, cx| {
26399        editor.handle_input("a", window, cx);
26400    });
26401    cx.wait_for_autoindent_applied().await;
26402    cx.assert_editor_state(indoc! {"
26403        def f() -> list[str]:
2640426405    "});
26406
26407    // test does not outdent on typing : after case keyword
26408    cx.set_state(indoc! {"
26409        match 1:
26410            caseˇ
26411    "});
26412    cx.update_editor(|editor, window, cx| {
26413        editor.handle_input(":", window, cx);
26414    });
26415    cx.wait_for_autoindent_applied().await;
26416    cx.assert_editor_state(indoc! {"
26417        match 1:
26418            case:ˇ
26419    "});
26420}
26421
26422#[gpui::test]
26423async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
26424    init_test(cx, |_| {});
26425    update_test_language_settings(cx, |settings| {
26426        settings.defaults.extend_comment_on_newline = Some(false);
26427    });
26428    let mut cx = EditorTestContext::new(cx).await;
26429    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
26430    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26431
26432    // test correct indent after newline on comment
26433    cx.set_state(indoc! {"
26434        # COMMENT:ˇ
26435    "});
26436    cx.update_editor(|editor, window, cx| {
26437        editor.newline(&Newline, window, cx);
26438    });
26439    cx.wait_for_autoindent_applied().await;
26440    cx.assert_editor_state(indoc! {"
26441        # COMMENT:
26442        ˇ
26443    "});
26444
26445    // test correct indent after newline in brackets
26446    cx.set_state(indoc! {"
26447        {ˇ}
26448    "});
26449    cx.update_editor(|editor, window, cx| {
26450        editor.newline(&Newline, window, cx);
26451    });
26452    cx.wait_for_autoindent_applied().await;
26453    cx.assert_editor_state(indoc! {"
26454        {
26455            ˇ
26456        }
26457    "});
26458
26459    cx.set_state(indoc! {"
26460        (ˇ)
26461    "});
26462    cx.update_editor(|editor, window, cx| {
26463        editor.newline(&Newline, window, cx);
26464    });
26465    cx.run_until_parked();
26466    cx.assert_editor_state(indoc! {"
26467        (
26468            ˇ
26469        )
26470    "});
26471
26472    // do not indent after empty lists or dictionaries
26473    cx.set_state(indoc! {"
26474        a = []ˇ
26475    "});
26476    cx.update_editor(|editor, window, cx| {
26477        editor.newline(&Newline, window, cx);
26478    });
26479    cx.run_until_parked();
26480    cx.assert_editor_state(indoc! {"
26481        a = []
26482        ˇ
26483    "});
26484}
26485
26486#[gpui::test]
26487async fn test_python_indent_in_markdown(cx: &mut TestAppContext) {
26488    init_test(cx, |_| {});
26489
26490    let language_registry = Arc::new(language::LanguageRegistry::test(cx.executor()));
26491    let python_lang = languages::language("python", tree_sitter_python::LANGUAGE.into());
26492    language_registry.add(markdown_lang());
26493    language_registry.add(python_lang);
26494
26495    let mut cx = EditorTestContext::new(cx).await;
26496    cx.update_buffer(|buffer, cx| {
26497        buffer.set_language_registry(language_registry);
26498        buffer.set_language(Some(markdown_lang()), cx);
26499    });
26500
26501    // Test that `else:` correctly outdents to match `if:` inside the Python code block
26502    cx.set_state(indoc! {"
26503        # Heading
26504
26505        ```python
26506        def main():
26507            if condition:
26508                pass
26509                ˇ
26510        ```
26511    "});
26512    cx.update_editor(|editor, window, cx| {
26513        editor.handle_input("else:", window, cx);
26514    });
26515    cx.run_until_parked();
26516    cx.assert_editor_state(indoc! {"
26517        # Heading
26518
26519        ```python
26520        def main():
26521            if condition:
26522                pass
26523            else:ˇ
26524        ```
26525    "});
26526}
26527
26528#[gpui::test]
26529async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
26530    init_test(cx, |_| {});
26531
26532    let mut cx = EditorTestContext::new(cx).await;
26533    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
26534    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26535
26536    // test cursor move to start of each line on tab
26537    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
26538    cx.set_state(indoc! {"
26539        function main() {
26540        ˇ    for item in $items; do
26541        ˇ        while [ -n \"$item\" ]; do
26542        ˇ            if [ \"$value\" -gt 10 ]; then
26543        ˇ                continue
26544        ˇ            elif [ \"$value\" -lt 0 ]; then
26545        ˇ                break
26546        ˇ            else
26547        ˇ                echo \"$item\"
26548        ˇ            fi
26549        ˇ        done
26550        ˇ    done
26551        ˇ}
26552    "});
26553    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
26554    cx.wait_for_autoindent_applied().await;
26555    cx.assert_editor_state(indoc! {"
26556        function main() {
26557            ˇfor item in $items; do
26558                ˇwhile [ -n \"$item\" ]; do
26559                    ˇif [ \"$value\" -gt 10 ]; then
26560                        ˇcontinue
26561                    ˇelif [ \"$value\" -lt 0 ]; then
26562                        ˇbreak
26563                    ˇelse
26564                        ˇecho \"$item\"
26565                    ˇfi
26566                ˇdone
26567            ˇdone
26568        ˇ}
26569    "});
26570    // test relative indent is preserved when tab
26571    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
26572    cx.wait_for_autoindent_applied().await;
26573    cx.assert_editor_state(indoc! {"
26574        function main() {
26575                ˇfor item in $items; do
26576                    ˇwhile [ -n \"$item\" ]; do
26577                        ˇif [ \"$value\" -gt 10 ]; then
26578                            ˇcontinue
26579                        ˇelif [ \"$value\" -lt 0 ]; then
26580                            ˇbreak
26581                        ˇelse
26582                            ˇecho \"$item\"
26583                        ˇfi
26584                    ˇdone
26585                ˇdone
26586            ˇ}
26587    "});
26588
26589    // test cursor move to start of each line on tab
26590    // for `case` statement with patterns
26591    cx.set_state(indoc! {"
26592        function handle() {
26593        ˇ    case \"$1\" in
26594        ˇ        start)
26595        ˇ            echo \"a\"
26596        ˇ            ;;
26597        ˇ        stop)
26598        ˇ            echo \"b\"
26599        ˇ            ;;
26600        ˇ        *)
26601        ˇ            echo \"c\"
26602        ˇ            ;;
26603        ˇ    esac
26604        ˇ}
26605    "});
26606    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
26607    cx.wait_for_autoindent_applied().await;
26608    cx.assert_editor_state(indoc! {"
26609        function handle() {
26610            ˇcase \"$1\" in
26611                ˇstart)
26612                    ˇecho \"a\"
26613                    ˇ;;
26614                ˇstop)
26615                    ˇecho \"b\"
26616                    ˇ;;
26617                ˇ*)
26618                    ˇecho \"c\"
26619                    ˇ;;
26620            ˇesac
26621        ˇ}
26622    "});
26623}
26624
26625#[gpui::test]
26626async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
26627    init_test(cx, |_| {});
26628
26629    let mut cx = EditorTestContext::new(cx).await;
26630    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
26631    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26632
26633    // test indents on comment insert
26634    cx.set_state(indoc! {"
26635        function main() {
26636        ˇ    for item in $items; do
26637        ˇ        while [ -n \"$item\" ]; do
26638        ˇ            if [ \"$value\" -gt 10 ]; then
26639        ˇ                continue
26640        ˇ            elif [ \"$value\" -lt 0 ]; then
26641        ˇ                break
26642        ˇ            else
26643        ˇ                echo \"$item\"
26644        ˇ            fi
26645        ˇ        done
26646        ˇ    done
26647        ˇ}
26648    "});
26649    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
26650    cx.wait_for_autoindent_applied().await;
26651    cx.assert_editor_state(indoc! {"
26652        function main() {
26653        #ˇ    for item in $items; do
26654        #ˇ        while [ -n \"$item\" ]; do
26655        #ˇ            if [ \"$value\" -gt 10 ]; then
26656        #ˇ                continue
26657        #ˇ            elif [ \"$value\" -lt 0 ]; then
26658        #ˇ                break
26659        #ˇ            else
26660        #ˇ                echo \"$item\"
26661        #ˇ            fi
26662        #ˇ        done
26663        #ˇ    done
26664        #ˇ}
26665    "});
26666}
26667
26668#[gpui::test]
26669async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
26670    init_test(cx, |_| {});
26671
26672    let mut cx = EditorTestContext::new(cx).await;
26673    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
26674    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26675
26676    // test `else` auto outdents when typed inside `if` block
26677    cx.set_state(indoc! {"
26678        if [ \"$1\" = \"test\" ]; then
26679            echo \"foo bar\"
26680            ˇ
26681    "});
26682    cx.update_editor(|editor, window, cx| {
26683        editor.handle_input("else", window, cx);
26684    });
26685    cx.wait_for_autoindent_applied().await;
26686    cx.assert_editor_state(indoc! {"
26687        if [ \"$1\" = \"test\" ]; then
26688            echo \"foo bar\"
26689        elseˇ
26690    "});
26691
26692    // test `elif` auto outdents when typed inside `if` block
26693    cx.set_state(indoc! {"
26694        if [ \"$1\" = \"test\" ]; then
26695            echo \"foo bar\"
26696            ˇ
26697    "});
26698    cx.update_editor(|editor, window, cx| {
26699        editor.handle_input("elif", window, cx);
26700    });
26701    cx.wait_for_autoindent_applied().await;
26702    cx.assert_editor_state(indoc! {"
26703        if [ \"$1\" = \"test\" ]; then
26704            echo \"foo bar\"
26705        elifˇ
26706    "});
26707
26708    // test `fi` auto outdents when typed inside `else` block
26709    cx.set_state(indoc! {"
26710        if [ \"$1\" = \"test\" ]; then
26711            echo \"foo bar\"
26712        else
26713            echo \"bar baz\"
26714            ˇ
26715    "});
26716    cx.update_editor(|editor, window, cx| {
26717        editor.handle_input("fi", window, cx);
26718    });
26719    cx.wait_for_autoindent_applied().await;
26720    cx.assert_editor_state(indoc! {"
26721        if [ \"$1\" = \"test\" ]; then
26722            echo \"foo bar\"
26723        else
26724            echo \"bar baz\"
26725        fiˇ
26726    "});
26727
26728    // test `done` auto outdents when typed inside `while` block
26729    cx.set_state(indoc! {"
26730        while read line; do
26731            echo \"$line\"
26732            ˇ
26733    "});
26734    cx.update_editor(|editor, window, cx| {
26735        editor.handle_input("done", window, cx);
26736    });
26737    cx.wait_for_autoindent_applied().await;
26738    cx.assert_editor_state(indoc! {"
26739        while read line; do
26740            echo \"$line\"
26741        doneˇ
26742    "});
26743
26744    // test `done` auto outdents when typed inside `for` block
26745    cx.set_state(indoc! {"
26746        for file in *.txt; do
26747            cat \"$file\"
26748            ˇ
26749    "});
26750    cx.update_editor(|editor, window, cx| {
26751        editor.handle_input("done", window, cx);
26752    });
26753    cx.wait_for_autoindent_applied().await;
26754    cx.assert_editor_state(indoc! {"
26755        for file in *.txt; do
26756            cat \"$file\"
26757        doneˇ
26758    "});
26759
26760    // test `esac` auto outdents when typed inside `case` block
26761    cx.set_state(indoc! {"
26762        case \"$1\" in
26763            start)
26764                echo \"foo bar\"
26765                ;;
26766            stop)
26767                echo \"bar baz\"
26768                ;;
26769            ˇ
26770    "});
26771    cx.update_editor(|editor, window, cx| {
26772        editor.handle_input("esac", window, cx);
26773    });
26774    cx.wait_for_autoindent_applied().await;
26775    cx.assert_editor_state(indoc! {"
26776        case \"$1\" in
26777            start)
26778                echo \"foo bar\"
26779                ;;
26780            stop)
26781                echo \"bar baz\"
26782                ;;
26783        esacˇ
26784    "});
26785
26786    // test `*)` auto outdents when typed inside `case` block
26787    cx.set_state(indoc! {"
26788        case \"$1\" in
26789            start)
26790                echo \"foo bar\"
26791                ;;
26792                ˇ
26793    "});
26794    cx.update_editor(|editor, window, cx| {
26795        editor.handle_input("*)", window, cx);
26796    });
26797    cx.wait_for_autoindent_applied().await;
26798    cx.assert_editor_state(indoc! {"
26799        case \"$1\" in
26800            start)
26801                echo \"foo bar\"
26802                ;;
26803            *)ˇ
26804    "});
26805
26806    // test `fi` outdents to correct level with nested if blocks
26807    cx.set_state(indoc! {"
26808        if [ \"$1\" = \"test\" ]; then
26809            echo \"outer if\"
26810            if [ \"$2\" = \"debug\" ]; then
26811                echo \"inner if\"
26812                ˇ
26813    "});
26814    cx.update_editor(|editor, window, cx| {
26815        editor.handle_input("fi", window, cx);
26816    });
26817    cx.wait_for_autoindent_applied().await;
26818    cx.assert_editor_state(indoc! {"
26819        if [ \"$1\" = \"test\" ]; then
26820            echo \"outer if\"
26821            if [ \"$2\" = \"debug\" ]; then
26822                echo \"inner if\"
26823            fiˇ
26824    "});
26825}
26826
26827#[gpui::test]
26828async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
26829    init_test(cx, |_| {});
26830    update_test_language_settings(cx, |settings| {
26831        settings.defaults.extend_comment_on_newline = Some(false);
26832    });
26833    let mut cx = EditorTestContext::new(cx).await;
26834    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
26835    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26836
26837    // test correct indent after newline on comment
26838    cx.set_state(indoc! {"
26839        # COMMENT:ˇ
26840    "});
26841    cx.update_editor(|editor, window, cx| {
26842        editor.newline(&Newline, window, cx);
26843    });
26844    cx.wait_for_autoindent_applied().await;
26845    cx.assert_editor_state(indoc! {"
26846        # COMMENT:
26847        ˇ
26848    "});
26849
26850    // test correct indent after newline after `then`
26851    cx.set_state(indoc! {"
26852
26853        if [ \"$1\" = \"test\" ]; thenˇ
26854    "});
26855    cx.update_editor(|editor, window, cx| {
26856        editor.newline(&Newline, window, cx);
26857    });
26858    cx.wait_for_autoindent_applied().await;
26859    cx.assert_editor_state(indoc! {"
26860
26861        if [ \"$1\" = \"test\" ]; then
26862            ˇ
26863    "});
26864
26865    // test correct indent after newline after `else`
26866    cx.set_state(indoc! {"
26867        if [ \"$1\" = \"test\" ]; then
26868        elseˇ
26869    "});
26870    cx.update_editor(|editor, window, cx| {
26871        editor.newline(&Newline, window, cx);
26872    });
26873    cx.wait_for_autoindent_applied().await;
26874    cx.assert_editor_state(indoc! {"
26875        if [ \"$1\" = \"test\" ]; then
26876        else
26877            ˇ
26878    "});
26879
26880    // test correct indent after newline after `elif`
26881    cx.set_state(indoc! {"
26882        if [ \"$1\" = \"test\" ]; then
26883        elifˇ
26884    "});
26885    cx.update_editor(|editor, window, cx| {
26886        editor.newline(&Newline, window, cx);
26887    });
26888    cx.wait_for_autoindent_applied().await;
26889    cx.assert_editor_state(indoc! {"
26890        if [ \"$1\" = \"test\" ]; then
26891        elif
26892            ˇ
26893    "});
26894
26895    // test correct indent after newline after `do`
26896    cx.set_state(indoc! {"
26897        for file in *.txt; doˇ
26898    "});
26899    cx.update_editor(|editor, window, cx| {
26900        editor.newline(&Newline, window, cx);
26901    });
26902    cx.wait_for_autoindent_applied().await;
26903    cx.assert_editor_state(indoc! {"
26904        for file in *.txt; do
26905            ˇ
26906    "});
26907
26908    // test correct indent after newline after case pattern
26909    cx.set_state(indoc! {"
26910        case \"$1\" in
26911            start)ˇ
26912    "});
26913    cx.update_editor(|editor, window, cx| {
26914        editor.newline(&Newline, window, cx);
26915    });
26916    cx.wait_for_autoindent_applied().await;
26917    cx.assert_editor_state(indoc! {"
26918        case \"$1\" in
26919            start)
26920                ˇ
26921    "});
26922
26923    // test correct indent after newline after case pattern
26924    cx.set_state(indoc! {"
26925        case \"$1\" in
26926            start)
26927                ;;
26928            *)ˇ
26929    "});
26930    cx.update_editor(|editor, window, cx| {
26931        editor.newline(&Newline, window, cx);
26932    });
26933    cx.wait_for_autoindent_applied().await;
26934    cx.assert_editor_state(indoc! {"
26935        case \"$1\" in
26936            start)
26937                ;;
26938            *)
26939                ˇ
26940    "});
26941
26942    // test correct indent after newline after function opening brace
26943    cx.set_state(indoc! {"
26944        function test() {ˇ}
26945    "});
26946    cx.update_editor(|editor, window, cx| {
26947        editor.newline(&Newline, window, cx);
26948    });
26949    cx.wait_for_autoindent_applied().await;
26950    cx.assert_editor_state(indoc! {"
26951        function test() {
26952            ˇ
26953        }
26954    "});
26955
26956    // test no extra indent after semicolon on same line
26957    cx.set_state(indoc! {"
26958        echo \"test\"26959    "});
26960    cx.update_editor(|editor, window, cx| {
26961        editor.newline(&Newline, window, cx);
26962    });
26963    cx.wait_for_autoindent_applied().await;
26964    cx.assert_editor_state(indoc! {"
26965        echo \"test\";
26966        ˇ
26967    "});
26968}
26969
26970fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
26971    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
26972    point..point
26973}
26974
26975#[track_caller]
26976fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
26977    let (text, ranges) = marked_text_ranges(marked_text, true);
26978    assert_eq!(editor.text(cx), text);
26979    assert_eq!(
26980        editor.selections.ranges(&editor.display_snapshot(cx)),
26981        ranges
26982            .iter()
26983            .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
26984            .collect::<Vec<_>>(),
26985        "Assert selections are {}",
26986        marked_text
26987    );
26988}
26989
26990pub fn handle_signature_help_request(
26991    cx: &mut EditorLspTestContext,
26992    mocked_response: lsp::SignatureHelp,
26993) -> impl Future<Output = ()> + use<> {
26994    let mut request =
26995        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
26996            let mocked_response = mocked_response.clone();
26997            async move { Ok(Some(mocked_response)) }
26998        });
26999
27000    async move {
27001        request.next().await;
27002    }
27003}
27004
27005#[track_caller]
27006pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
27007    cx.update_editor(|editor, _, _| {
27008        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
27009            let entries = menu.entries.borrow();
27010            let entries = entries
27011                .iter()
27012                .map(|entry| entry.string.as_str())
27013                .collect::<Vec<_>>();
27014            assert_eq!(entries, expected);
27015        } else {
27016            panic!("Expected completions menu");
27017        }
27018    });
27019}
27020
27021#[gpui::test]
27022async fn test_mixed_completions_with_multi_word_snippet(cx: &mut TestAppContext) {
27023    init_test(cx, |_| {});
27024    let mut cx = EditorLspTestContext::new_rust(
27025        lsp::ServerCapabilities {
27026            completion_provider: Some(lsp::CompletionOptions {
27027                ..Default::default()
27028            }),
27029            ..Default::default()
27030        },
27031        cx,
27032    )
27033    .await;
27034    cx.lsp
27035        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
27036            Ok(Some(lsp::CompletionResponse::Array(vec![
27037                lsp::CompletionItem {
27038                    label: "unsafe".into(),
27039                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
27040                        range: lsp::Range {
27041                            start: lsp::Position {
27042                                line: 0,
27043                                character: 9,
27044                            },
27045                            end: lsp::Position {
27046                                line: 0,
27047                                character: 11,
27048                            },
27049                        },
27050                        new_text: "unsafe".to_string(),
27051                    })),
27052                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
27053                    ..Default::default()
27054                },
27055            ])))
27056        });
27057
27058    cx.update_editor(|editor, _, cx| {
27059        editor.project().unwrap().update(cx, |project, cx| {
27060            project.snippets().update(cx, |snippets, _cx| {
27061                snippets.add_snippet_for_test(
27062                    None,
27063                    PathBuf::from("test_snippets.json"),
27064                    vec![
27065                        Arc::new(project::snippet_provider::Snippet {
27066                            prefix: vec![
27067                                "unlimited word count".to_string(),
27068                                "unlimit word count".to_string(),
27069                                "unlimited unknown".to_string(),
27070                            ],
27071                            body: "this is many words".to_string(),
27072                            description: Some("description".to_string()),
27073                            name: "multi-word snippet test".to_string(),
27074                        }),
27075                        Arc::new(project::snippet_provider::Snippet {
27076                            prefix: vec!["unsnip".to_string(), "@few".to_string()],
27077                            body: "fewer words".to_string(),
27078                            description: Some("alt description".to_string()),
27079                            name: "other name".to_string(),
27080                        }),
27081                        Arc::new(project::snippet_provider::Snippet {
27082                            prefix: vec!["ab aa".to_string()],
27083                            body: "abcd".to_string(),
27084                            description: None,
27085                            name: "alphabet".to_string(),
27086                        }),
27087                    ],
27088                );
27089            });
27090        })
27091    });
27092
27093    let get_completions = |cx: &mut EditorLspTestContext| {
27094        cx.update_editor(|editor, _, _| match &*editor.context_menu.borrow() {
27095            Some(CodeContextMenu::Completions(context_menu)) => {
27096                let entries = context_menu.entries.borrow();
27097                entries
27098                    .iter()
27099                    .map(|entry| entry.string.clone())
27100                    .collect_vec()
27101            }
27102            _ => vec![],
27103        })
27104    };
27105
27106    // snippets:
27107    //  @foo
27108    //  foo bar
27109    //
27110    // when typing:
27111    //
27112    // when typing:
27113    //  - if I type a symbol "open the completions with snippets only"
27114    //  - if I type a word character "open the completions menu" (if it had been open snippets only, clear it out)
27115    //
27116    // stuff we need:
27117    //  - filtering logic change?
27118    //  - remember how far back the completion started.
27119
27120    let test_cases: &[(&str, &[&str])] = &[
27121        (
27122            "un",
27123            &[
27124                "unsafe",
27125                "unlimit word count",
27126                "unlimited unknown",
27127                "unlimited word count",
27128                "unsnip",
27129            ],
27130        ),
27131        (
27132            "u ",
27133            &[
27134                "unlimit word count",
27135                "unlimited unknown",
27136                "unlimited word count",
27137            ],
27138        ),
27139        ("u a", &["ab aa", "unsafe"]), // unsAfe
27140        (
27141            "u u",
27142            &[
27143                "unsafe",
27144                "unlimit word count",
27145                "unlimited unknown", // ranked highest among snippets
27146                "unlimited word count",
27147                "unsnip",
27148            ],
27149        ),
27150        ("uw c", &["unlimit word count", "unlimited word count"]),
27151        (
27152            "u w",
27153            &[
27154                "unlimit word count",
27155                "unlimited word count",
27156                "unlimited unknown",
27157            ],
27158        ),
27159        ("u w ", &["unlimit word count", "unlimited word count"]),
27160        (
27161            "u ",
27162            &[
27163                "unlimit word count",
27164                "unlimited unknown",
27165                "unlimited word count",
27166            ],
27167        ),
27168        ("wor", &[]),
27169        ("uf", &["unsafe"]),
27170        ("af", &["unsafe"]),
27171        ("afu", &[]),
27172        (
27173            "ue",
27174            &["unsafe", "unlimited unknown", "unlimited word count"],
27175        ),
27176        ("@", &["@few"]),
27177        ("@few", &["@few"]),
27178        ("@ ", &[]),
27179        ("a@", &["@few"]),
27180        ("a@f", &["@few", "unsafe"]),
27181        ("a@fw", &["@few"]),
27182        ("a", &["ab aa", "unsafe"]),
27183        ("aa", &["ab aa"]),
27184        ("aaa", &["ab aa"]),
27185        ("ab", &["ab aa"]),
27186        ("ab ", &["ab aa"]),
27187        ("ab a", &["ab aa", "unsafe"]),
27188        ("ab ab", &["ab aa"]),
27189        ("ab ab aa", &["ab aa"]),
27190    ];
27191
27192    for &(input_to_simulate, expected_completions) in test_cases {
27193        cx.set_state("fn a() { ˇ }\n");
27194        for c in input_to_simulate.split("") {
27195            cx.simulate_input(c);
27196            cx.run_until_parked();
27197        }
27198        let expected_completions = expected_completions
27199            .iter()
27200            .map(|s| s.to_string())
27201            .collect_vec();
27202        assert_eq!(
27203            get_completions(&mut cx),
27204            expected_completions,
27205            "< actual / expected >, input = {input_to_simulate:?}",
27206        );
27207    }
27208}
27209
27210/// Handle completion request passing a marked string specifying where the completion
27211/// should be triggered from using '|' character, what range should be replaced, and what completions
27212/// should be returned using '<' and '>' to delimit the range.
27213///
27214/// Also see `handle_completion_request_with_insert_and_replace`.
27215#[track_caller]
27216pub fn handle_completion_request(
27217    marked_string: &str,
27218    completions: Vec<&'static str>,
27219    is_incomplete: bool,
27220    counter: Arc<AtomicUsize>,
27221    cx: &mut EditorLspTestContext,
27222) -> impl Future<Output = ()> {
27223    let complete_from_marker: TextRangeMarker = '|'.into();
27224    let replace_range_marker: TextRangeMarker = ('<', '>').into();
27225    let (_, mut marked_ranges) = marked_text_ranges_by(
27226        marked_string,
27227        vec![complete_from_marker.clone(), replace_range_marker.clone()],
27228    );
27229
27230    let complete_from_position = cx.to_lsp(MultiBufferOffset(
27231        marked_ranges.remove(&complete_from_marker).unwrap()[0].start,
27232    ));
27233    let range = marked_ranges.remove(&replace_range_marker).unwrap()[0].clone();
27234    let replace_range =
27235        cx.to_lsp_range(MultiBufferOffset(range.start)..MultiBufferOffset(range.end));
27236
27237    let mut request =
27238        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
27239            let completions = completions.clone();
27240            counter.fetch_add(1, atomic::Ordering::Release);
27241            async move {
27242                assert_eq!(params.text_document_position.text_document.uri, url.clone());
27243                assert_eq!(
27244                    params.text_document_position.position,
27245                    complete_from_position
27246                );
27247                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
27248                    is_incomplete,
27249                    item_defaults: None,
27250                    items: completions
27251                        .iter()
27252                        .map(|completion_text| lsp::CompletionItem {
27253                            label: completion_text.to_string(),
27254                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
27255                                range: replace_range,
27256                                new_text: completion_text.to_string(),
27257                            })),
27258                            ..Default::default()
27259                        })
27260                        .collect(),
27261                })))
27262            }
27263        });
27264
27265    async move {
27266        request.next().await;
27267    }
27268}
27269
27270/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
27271/// given instead, which also contains an `insert` range.
27272///
27273/// This function uses markers to define ranges:
27274/// - `|` marks the cursor position
27275/// - `<>` marks the replace range
27276/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
27277pub fn handle_completion_request_with_insert_and_replace(
27278    cx: &mut EditorLspTestContext,
27279    marked_string: &str,
27280    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
27281    counter: Arc<AtomicUsize>,
27282) -> impl Future<Output = ()> {
27283    let complete_from_marker: TextRangeMarker = '|'.into();
27284    let replace_range_marker: TextRangeMarker = ('<', '>').into();
27285    let insert_range_marker: TextRangeMarker = ('{', '}').into();
27286
27287    let (_, mut marked_ranges) = marked_text_ranges_by(
27288        marked_string,
27289        vec![
27290            complete_from_marker.clone(),
27291            replace_range_marker.clone(),
27292            insert_range_marker.clone(),
27293        ],
27294    );
27295
27296    let complete_from_position = cx.to_lsp(MultiBufferOffset(
27297        marked_ranges.remove(&complete_from_marker).unwrap()[0].start,
27298    ));
27299    let range = marked_ranges.remove(&replace_range_marker).unwrap()[0].clone();
27300    let replace_range =
27301        cx.to_lsp_range(MultiBufferOffset(range.start)..MultiBufferOffset(range.end));
27302
27303    let insert_range = match marked_ranges.remove(&insert_range_marker) {
27304        Some(ranges) if !ranges.is_empty() => {
27305            let range1 = ranges[0].clone();
27306            cx.to_lsp_range(MultiBufferOffset(range1.start)..MultiBufferOffset(range1.end))
27307        }
27308        _ => lsp::Range {
27309            start: replace_range.start,
27310            end: complete_from_position,
27311        },
27312    };
27313
27314    let mut request =
27315        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
27316            let completions = completions.clone();
27317            counter.fetch_add(1, atomic::Ordering::Release);
27318            async move {
27319                assert_eq!(params.text_document_position.text_document.uri, url.clone());
27320                assert_eq!(
27321                    params.text_document_position.position, complete_from_position,
27322                    "marker `|` position doesn't match",
27323                );
27324                Ok(Some(lsp::CompletionResponse::Array(
27325                    completions
27326                        .iter()
27327                        .map(|(label, new_text)| lsp::CompletionItem {
27328                            label: label.to_string(),
27329                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
27330                                lsp::InsertReplaceEdit {
27331                                    insert: insert_range,
27332                                    replace: replace_range,
27333                                    new_text: new_text.to_string(),
27334                                },
27335                            )),
27336                            ..Default::default()
27337                        })
27338                        .collect(),
27339                )))
27340            }
27341        });
27342
27343    async move {
27344        request.next().await;
27345    }
27346}
27347
27348fn handle_resolve_completion_request(
27349    cx: &mut EditorLspTestContext,
27350    edits: Option<Vec<(&'static str, &'static str)>>,
27351) -> impl Future<Output = ()> {
27352    let edits = edits.map(|edits| {
27353        edits
27354            .iter()
27355            .map(|(marked_string, new_text)| {
27356                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
27357                let replace_range = cx.to_lsp_range(
27358                    MultiBufferOffset(marked_ranges[0].start)
27359                        ..MultiBufferOffset(marked_ranges[0].end),
27360                );
27361                lsp::TextEdit::new(replace_range, new_text.to_string())
27362            })
27363            .collect::<Vec<_>>()
27364    });
27365
27366    let mut request =
27367        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
27368            let edits = edits.clone();
27369            async move {
27370                Ok(lsp::CompletionItem {
27371                    additional_text_edits: edits,
27372                    ..Default::default()
27373                })
27374            }
27375        });
27376
27377    async move {
27378        request.next().await;
27379    }
27380}
27381
27382pub(crate) fn update_test_language_settings(
27383    cx: &mut TestAppContext,
27384    f: impl Fn(&mut AllLanguageSettingsContent),
27385) {
27386    cx.update(|cx| {
27387        SettingsStore::update_global(cx, |store, cx| {
27388            store.update_user_settings(cx, |settings| f(&mut settings.project.all_languages));
27389        });
27390    });
27391}
27392
27393pub(crate) fn update_test_project_settings(
27394    cx: &mut TestAppContext,
27395    f: impl Fn(&mut ProjectSettingsContent),
27396) {
27397    cx.update(|cx| {
27398        SettingsStore::update_global(cx, |store, cx| {
27399            store.update_user_settings(cx, |settings| f(&mut settings.project));
27400        });
27401    });
27402}
27403
27404pub(crate) fn update_test_editor_settings(
27405    cx: &mut TestAppContext,
27406    f: impl Fn(&mut EditorSettingsContent),
27407) {
27408    cx.update(|cx| {
27409        SettingsStore::update_global(cx, |store, cx| {
27410            store.update_user_settings(cx, |settings| f(&mut settings.editor));
27411        })
27412    })
27413}
27414
27415pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
27416    cx.update(|cx| {
27417        assets::Assets.load_test_fonts(cx);
27418        let store = SettingsStore::test(cx);
27419        cx.set_global(store);
27420        theme::init(theme::LoadThemes::JustBase, cx);
27421        release_channel::init(semver::Version::new(0, 0, 0), cx);
27422        crate::init(cx);
27423    });
27424    zlog::init_test();
27425    update_test_language_settings(cx, f);
27426}
27427
27428#[track_caller]
27429fn assert_hunk_revert(
27430    not_reverted_text_with_selections: &str,
27431    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
27432    expected_reverted_text_with_selections: &str,
27433    base_text: &str,
27434    cx: &mut EditorLspTestContext,
27435) {
27436    cx.set_state(not_reverted_text_with_selections);
27437    cx.set_head_text(base_text);
27438    cx.executor().run_until_parked();
27439
27440    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
27441        let snapshot = editor.snapshot(window, cx);
27442        let reverted_hunk_statuses = snapshot
27443            .buffer_snapshot()
27444            .diff_hunks_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
27445            .map(|hunk| hunk.status().kind)
27446            .collect::<Vec<_>>();
27447
27448        editor.git_restore(&Default::default(), window, cx);
27449        reverted_hunk_statuses
27450    });
27451    cx.executor().run_until_parked();
27452    cx.assert_editor_state(expected_reverted_text_with_selections);
27453    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
27454}
27455
27456#[gpui::test(iterations = 10)]
27457async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
27458    init_test(cx, |_| {});
27459
27460    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
27461    let counter = diagnostic_requests.clone();
27462
27463    let fs = FakeFs::new(cx.executor());
27464    fs.insert_tree(
27465        path!("/a"),
27466        json!({
27467            "first.rs": "fn main() { let a = 5; }",
27468            "second.rs": "// Test file",
27469        }),
27470    )
27471    .await;
27472
27473    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
27474    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
27475    let cx = &mut VisualTestContext::from_window(*workspace, cx);
27476
27477    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
27478    language_registry.add(rust_lang());
27479    let mut fake_servers = language_registry.register_fake_lsp(
27480        "Rust",
27481        FakeLspAdapter {
27482            capabilities: lsp::ServerCapabilities {
27483                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
27484                    lsp::DiagnosticOptions {
27485                        identifier: None,
27486                        inter_file_dependencies: true,
27487                        workspace_diagnostics: true,
27488                        work_done_progress_options: Default::default(),
27489                    },
27490                )),
27491                ..Default::default()
27492            },
27493            ..Default::default()
27494        },
27495    );
27496
27497    let editor = workspace
27498        .update(cx, |workspace, window, cx| {
27499            workspace.open_abs_path(
27500                PathBuf::from(path!("/a/first.rs")),
27501                OpenOptions::default(),
27502                window,
27503                cx,
27504            )
27505        })
27506        .unwrap()
27507        .await
27508        .unwrap()
27509        .downcast::<Editor>()
27510        .unwrap();
27511    let fake_server = fake_servers.next().await.unwrap();
27512    let server_id = fake_server.server.server_id();
27513    let mut first_request = fake_server
27514        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
27515            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
27516            let result_id = Some(new_result_id.to_string());
27517            assert_eq!(
27518                params.text_document.uri,
27519                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
27520            );
27521            async move {
27522                Ok(lsp::DocumentDiagnosticReportResult::Report(
27523                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
27524                        related_documents: None,
27525                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
27526                            items: Vec::new(),
27527                            result_id,
27528                        },
27529                    }),
27530                ))
27531            }
27532        });
27533
27534    let ensure_result_id = |expected: Option<SharedString>, cx: &mut TestAppContext| {
27535        project.update(cx, |project, cx| {
27536            let buffer_id = editor
27537                .read(cx)
27538                .buffer()
27539                .read(cx)
27540                .as_singleton()
27541                .expect("created a singleton buffer")
27542                .read(cx)
27543                .remote_id();
27544            let buffer_result_id = project
27545                .lsp_store()
27546                .read(cx)
27547                .result_id_for_buffer_pull(server_id, buffer_id, &None, cx);
27548            assert_eq!(expected, buffer_result_id);
27549        });
27550    };
27551
27552    ensure_result_id(None, cx);
27553    cx.executor().advance_clock(Duration::from_millis(60));
27554    cx.executor().run_until_parked();
27555    assert_eq!(
27556        diagnostic_requests.load(atomic::Ordering::Acquire),
27557        1,
27558        "Opening file should trigger diagnostic request"
27559    );
27560    first_request
27561        .next()
27562        .await
27563        .expect("should have sent the first diagnostics pull request");
27564    ensure_result_id(Some(SharedString::new("1")), cx);
27565
27566    // Editing should trigger diagnostics
27567    editor.update_in(cx, |editor, window, cx| {
27568        editor.handle_input("2", window, cx)
27569    });
27570    cx.executor().advance_clock(Duration::from_millis(60));
27571    cx.executor().run_until_parked();
27572    assert_eq!(
27573        diagnostic_requests.load(atomic::Ordering::Acquire),
27574        2,
27575        "Editing should trigger diagnostic request"
27576    );
27577    ensure_result_id(Some(SharedString::new("2")), cx);
27578
27579    // Moving cursor should not trigger diagnostic request
27580    editor.update_in(cx, |editor, window, cx| {
27581        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
27582            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
27583        });
27584    });
27585    cx.executor().advance_clock(Duration::from_millis(60));
27586    cx.executor().run_until_parked();
27587    assert_eq!(
27588        diagnostic_requests.load(atomic::Ordering::Acquire),
27589        2,
27590        "Cursor movement should not trigger diagnostic request"
27591    );
27592    ensure_result_id(Some(SharedString::new("2")), cx);
27593    // Multiple rapid edits should be debounced
27594    for _ in 0..5 {
27595        editor.update_in(cx, |editor, window, cx| {
27596            editor.handle_input("x", window, cx)
27597        });
27598    }
27599    cx.executor().advance_clock(Duration::from_millis(60));
27600    cx.executor().run_until_parked();
27601
27602    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
27603    assert!(
27604        final_requests <= 4,
27605        "Multiple rapid edits should be debounced (got {final_requests} requests)",
27606    );
27607    ensure_result_id(Some(SharedString::new(final_requests.to_string())), cx);
27608}
27609
27610#[gpui::test]
27611async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
27612    // Regression test for issue #11671
27613    // Previously, adding a cursor after moving multiple cursors would reset
27614    // the cursor count instead of adding to the existing cursors.
27615    init_test(cx, |_| {});
27616    let mut cx = EditorTestContext::new(cx).await;
27617
27618    // Create a simple buffer with cursor at start
27619    cx.set_state(indoc! {"
27620        ˇaaaa
27621        bbbb
27622        cccc
27623        dddd
27624        eeee
27625        ffff
27626        gggg
27627        hhhh"});
27628
27629    // Add 2 cursors below (so we have 3 total)
27630    cx.update_editor(|editor, window, cx| {
27631        editor.add_selection_below(&Default::default(), window, cx);
27632        editor.add_selection_below(&Default::default(), window, cx);
27633    });
27634
27635    // Verify we have 3 cursors
27636    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
27637    assert_eq!(
27638        initial_count, 3,
27639        "Should have 3 cursors after adding 2 below"
27640    );
27641
27642    // Move down one line
27643    cx.update_editor(|editor, window, cx| {
27644        editor.move_down(&MoveDown, window, cx);
27645    });
27646
27647    // Add another cursor below
27648    cx.update_editor(|editor, window, cx| {
27649        editor.add_selection_below(&Default::default(), window, cx);
27650    });
27651
27652    // Should now have 4 cursors (3 original + 1 new)
27653    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
27654    assert_eq!(
27655        final_count, 4,
27656        "Should have 4 cursors after moving and adding another"
27657    );
27658}
27659
27660#[gpui::test]
27661async fn test_add_selection_skip_soft_wrap_option(cx: &mut TestAppContext) {
27662    init_test(cx, |_| {});
27663
27664    let mut cx = EditorTestContext::new(cx).await;
27665
27666    cx.set_state(indoc!(
27667        r#"ˇThis is a very long line that will be wrapped when soft wrapping is enabled
27668           Second line here"#
27669    ));
27670
27671    cx.update_editor(|editor, window, cx| {
27672        // Enable soft wrapping with a narrow width to force soft wrapping and
27673        // confirm that more than 2 rows are being displayed.
27674        editor.set_wrap_width(Some(100.0.into()), cx);
27675        assert!(editor.display_text(cx).lines().count() > 2);
27676
27677        editor.add_selection_below(
27678            &AddSelectionBelow {
27679                skip_soft_wrap: true,
27680            },
27681            window,
27682            cx,
27683        );
27684
27685        assert_eq!(
27686            display_ranges(editor, cx),
27687            &[
27688                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
27689                DisplayPoint::new(DisplayRow(8), 0)..DisplayPoint::new(DisplayRow(8), 0),
27690            ]
27691        );
27692
27693        editor.add_selection_above(
27694            &AddSelectionAbove {
27695                skip_soft_wrap: true,
27696            },
27697            window,
27698            cx,
27699        );
27700
27701        assert_eq!(
27702            display_ranges(editor, cx),
27703            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
27704        );
27705
27706        editor.add_selection_below(
27707            &AddSelectionBelow {
27708                skip_soft_wrap: false,
27709            },
27710            window,
27711            cx,
27712        );
27713
27714        assert_eq!(
27715            display_ranges(editor, cx),
27716            &[
27717                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
27718                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
27719            ]
27720        );
27721
27722        editor.add_selection_above(
27723            &AddSelectionAbove {
27724                skip_soft_wrap: false,
27725            },
27726            window,
27727            cx,
27728        );
27729
27730        assert_eq!(
27731            display_ranges(editor, cx),
27732            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
27733        );
27734    });
27735
27736    // Set up text where selections are in the middle of a soft-wrapped line.
27737    // When adding selection below with `skip_soft_wrap` set to `true`, the new
27738    // selection should be at the same buffer column, not the same pixel
27739    // position.
27740    cx.set_state(indoc!(
27741        r#"1. Very long line to show «howˇ» a wrapped line would look
27742           2. Very long line to show how a wrapped line would look"#
27743    ));
27744
27745    cx.update_editor(|editor, window, cx| {
27746        // Enable soft wrapping with a narrow width to force soft wrapping and
27747        // confirm that more than 2 rows are being displayed.
27748        editor.set_wrap_width(Some(100.0.into()), cx);
27749        assert!(editor.display_text(cx).lines().count() > 2);
27750
27751        editor.add_selection_below(
27752            &AddSelectionBelow {
27753                skip_soft_wrap: true,
27754            },
27755            window,
27756            cx,
27757        );
27758
27759        // Assert that there's now 2 selections, both selecting the same column
27760        // range in the buffer row.
27761        let display_map = editor.display_map.update(cx, |map, cx| map.snapshot(cx));
27762        let selections = editor.selections.all::<Point>(&display_map);
27763        assert_eq!(selections.len(), 2);
27764        assert_eq!(selections[0].start.column, selections[1].start.column);
27765        assert_eq!(selections[0].end.column, selections[1].end.column);
27766    });
27767}
27768
27769#[gpui::test]
27770async fn test_insert_snippet(cx: &mut TestAppContext) {
27771    init_test(cx, |_| {});
27772    let mut cx = EditorTestContext::new(cx).await;
27773
27774    cx.update_editor(|editor, _, cx| {
27775        editor.project().unwrap().update(cx, |project, cx| {
27776            project.snippets().update(cx, |snippets, _cx| {
27777                let snippet = project::snippet_provider::Snippet {
27778                    prefix: vec![], // no prefix needed!
27779                    body: "an Unspecified".to_string(),
27780                    description: Some("shhhh it's a secret".to_string()),
27781                    name: "super secret snippet".to_string(),
27782                };
27783                snippets.add_snippet_for_test(
27784                    None,
27785                    PathBuf::from("test_snippets.json"),
27786                    vec![Arc::new(snippet)],
27787                );
27788
27789                let snippet = project::snippet_provider::Snippet {
27790                    prefix: vec![], // no prefix needed!
27791                    body: " Location".to_string(),
27792                    description: Some("the word 'location'".to_string()),
27793                    name: "location word".to_string(),
27794                };
27795                snippets.add_snippet_for_test(
27796                    Some("Markdown".to_string()),
27797                    PathBuf::from("test_snippets.json"),
27798                    vec![Arc::new(snippet)],
27799                );
27800            });
27801        })
27802    });
27803
27804    cx.set_state(indoc!(r#"First cursor at ˇ and second cursor at ˇ"#));
27805
27806    cx.update_editor(|editor, window, cx| {
27807        editor.insert_snippet_at_selections(
27808            &InsertSnippet {
27809                language: None,
27810                name: Some("super secret snippet".to_string()),
27811                snippet: None,
27812            },
27813            window,
27814            cx,
27815        );
27816
27817        // Language is specified in the action,
27818        // so the buffer language does not need to match
27819        editor.insert_snippet_at_selections(
27820            &InsertSnippet {
27821                language: Some("Markdown".to_string()),
27822                name: Some("location word".to_string()),
27823                snippet: None,
27824            },
27825            window,
27826            cx,
27827        );
27828
27829        editor.insert_snippet_at_selections(
27830            &InsertSnippet {
27831                language: None,
27832                name: None,
27833                snippet: Some("$0 after".to_string()),
27834            },
27835            window,
27836            cx,
27837        );
27838    });
27839
27840    cx.assert_editor_state(
27841        r#"First cursor at an Unspecified Locationˇ after and second cursor at an Unspecified Locationˇ after"#,
27842    );
27843}
27844
27845#[gpui::test(iterations = 10)]
27846async fn test_document_colors(cx: &mut TestAppContext) {
27847    let expected_color = Rgba {
27848        r: 0.33,
27849        g: 0.33,
27850        b: 0.33,
27851        a: 0.33,
27852    };
27853
27854    init_test(cx, |_| {});
27855
27856    let fs = FakeFs::new(cx.executor());
27857    fs.insert_tree(
27858        path!("/a"),
27859        json!({
27860            "first.rs": "fn main() { let a = 5; }",
27861        }),
27862    )
27863    .await;
27864
27865    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
27866    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
27867    let cx = &mut VisualTestContext::from_window(*workspace, cx);
27868
27869    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
27870    language_registry.add(rust_lang());
27871    let mut fake_servers = language_registry.register_fake_lsp(
27872        "Rust",
27873        FakeLspAdapter {
27874            capabilities: lsp::ServerCapabilities {
27875                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
27876                ..lsp::ServerCapabilities::default()
27877            },
27878            name: "rust-analyzer",
27879            ..FakeLspAdapter::default()
27880        },
27881    );
27882    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
27883        "Rust",
27884        FakeLspAdapter {
27885            capabilities: lsp::ServerCapabilities {
27886                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
27887                ..lsp::ServerCapabilities::default()
27888            },
27889            name: "not-rust-analyzer",
27890            ..FakeLspAdapter::default()
27891        },
27892    );
27893
27894    let editor = workspace
27895        .update(cx, |workspace, window, cx| {
27896            workspace.open_abs_path(
27897                PathBuf::from(path!("/a/first.rs")),
27898                OpenOptions::default(),
27899                window,
27900                cx,
27901            )
27902        })
27903        .unwrap()
27904        .await
27905        .unwrap()
27906        .downcast::<Editor>()
27907        .unwrap();
27908    let fake_language_server = fake_servers.next().await.unwrap();
27909    let fake_language_server_without_capabilities =
27910        fake_servers_without_capabilities.next().await.unwrap();
27911    let requests_made = Arc::new(AtomicUsize::new(0));
27912    let closure_requests_made = Arc::clone(&requests_made);
27913    let mut color_request_handle = fake_language_server
27914        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
27915            let requests_made = Arc::clone(&closure_requests_made);
27916            async move {
27917                assert_eq!(
27918                    params.text_document.uri,
27919                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
27920                );
27921                requests_made.fetch_add(1, atomic::Ordering::Release);
27922                Ok(vec![
27923                    lsp::ColorInformation {
27924                        range: lsp::Range {
27925                            start: lsp::Position {
27926                                line: 0,
27927                                character: 0,
27928                            },
27929                            end: lsp::Position {
27930                                line: 0,
27931                                character: 1,
27932                            },
27933                        },
27934                        color: lsp::Color {
27935                            red: 0.33,
27936                            green: 0.33,
27937                            blue: 0.33,
27938                            alpha: 0.33,
27939                        },
27940                    },
27941                    lsp::ColorInformation {
27942                        range: lsp::Range {
27943                            start: lsp::Position {
27944                                line: 0,
27945                                character: 0,
27946                            },
27947                            end: lsp::Position {
27948                                line: 0,
27949                                character: 1,
27950                            },
27951                        },
27952                        color: lsp::Color {
27953                            red: 0.33,
27954                            green: 0.33,
27955                            blue: 0.33,
27956                            alpha: 0.33,
27957                        },
27958                    },
27959                ])
27960            }
27961        });
27962
27963    let _handle = fake_language_server_without_capabilities
27964        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
27965            panic!("Should not be called");
27966        });
27967    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
27968    color_request_handle.next().await.unwrap();
27969    cx.run_until_parked();
27970    assert_eq!(
27971        1,
27972        requests_made.load(atomic::Ordering::Acquire),
27973        "Should query for colors once per editor open"
27974    );
27975    editor.update_in(cx, |editor, _, cx| {
27976        assert_eq!(
27977            vec![expected_color],
27978            extract_color_inlays(editor, cx),
27979            "Should have an initial inlay"
27980        );
27981    });
27982
27983    // opening another file in a split should not influence the LSP query counter
27984    workspace
27985        .update(cx, |workspace, window, cx| {
27986            assert_eq!(
27987                workspace.panes().len(),
27988                1,
27989                "Should have one pane with one editor"
27990            );
27991            workspace.move_item_to_pane_in_direction(
27992                &MoveItemToPaneInDirection {
27993                    direction: SplitDirection::Right,
27994                    focus: false,
27995                    clone: true,
27996                },
27997                window,
27998                cx,
27999            );
28000        })
28001        .unwrap();
28002    cx.run_until_parked();
28003    workspace
28004        .update(cx, |workspace, _, cx| {
28005            let panes = workspace.panes();
28006            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
28007            for pane in panes {
28008                let editor = pane
28009                    .read(cx)
28010                    .active_item()
28011                    .and_then(|item| item.downcast::<Editor>())
28012                    .expect("Should have opened an editor in each split");
28013                let editor_file = editor
28014                    .read(cx)
28015                    .buffer()
28016                    .read(cx)
28017                    .as_singleton()
28018                    .expect("test deals with singleton buffers")
28019                    .read(cx)
28020                    .file()
28021                    .expect("test buffese should have a file")
28022                    .path();
28023                assert_eq!(
28024                    editor_file.as_ref(),
28025                    rel_path("first.rs"),
28026                    "Both editors should be opened for the same file"
28027                )
28028            }
28029        })
28030        .unwrap();
28031
28032    cx.executor().advance_clock(Duration::from_millis(500));
28033    let save = editor.update_in(cx, |editor, window, cx| {
28034        editor.move_to_end(&MoveToEnd, window, cx);
28035        editor.handle_input("dirty", window, cx);
28036        editor.save(
28037            SaveOptions {
28038                format: true,
28039                autosave: true,
28040            },
28041            project.clone(),
28042            window,
28043            cx,
28044        )
28045    });
28046    save.await.unwrap();
28047
28048    color_request_handle.next().await.unwrap();
28049    cx.run_until_parked();
28050    assert_eq!(
28051        2,
28052        requests_made.load(atomic::Ordering::Acquire),
28053        "Should query for colors once per save (deduplicated) and once per formatting after save"
28054    );
28055
28056    drop(editor);
28057    let close = workspace
28058        .update(cx, |workspace, window, cx| {
28059            workspace.active_pane().update(cx, |pane, cx| {
28060                pane.close_active_item(&CloseActiveItem::default(), window, cx)
28061            })
28062        })
28063        .unwrap();
28064    close.await.unwrap();
28065    let close = workspace
28066        .update(cx, |workspace, window, cx| {
28067            workspace.active_pane().update(cx, |pane, cx| {
28068                pane.close_active_item(&CloseActiveItem::default(), window, cx)
28069            })
28070        })
28071        .unwrap();
28072    close.await.unwrap();
28073    assert_eq!(
28074        2,
28075        requests_made.load(atomic::Ordering::Acquire),
28076        "After saving and closing all editors, no extra requests should be made"
28077    );
28078    workspace
28079        .update(cx, |workspace, _, cx| {
28080            assert!(
28081                workspace.active_item(cx).is_none(),
28082                "Should close all editors"
28083            )
28084        })
28085        .unwrap();
28086
28087    workspace
28088        .update(cx, |workspace, window, cx| {
28089            workspace.active_pane().update(cx, |pane, cx| {
28090                pane.navigate_backward(&workspace::GoBack, window, cx);
28091            })
28092        })
28093        .unwrap();
28094    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
28095    cx.run_until_parked();
28096    let editor = workspace
28097        .update(cx, |workspace, _, cx| {
28098            workspace
28099                .active_item(cx)
28100                .expect("Should have reopened the editor again after navigating back")
28101                .downcast::<Editor>()
28102                .expect("Should be an editor")
28103        })
28104        .unwrap();
28105
28106    assert_eq!(
28107        2,
28108        requests_made.load(atomic::Ordering::Acquire),
28109        "Cache should be reused on buffer close and reopen"
28110    );
28111    editor.update(cx, |editor, cx| {
28112        assert_eq!(
28113            vec![expected_color],
28114            extract_color_inlays(editor, cx),
28115            "Should have an initial inlay"
28116        );
28117    });
28118
28119    drop(color_request_handle);
28120    let closure_requests_made = Arc::clone(&requests_made);
28121    let mut empty_color_request_handle = fake_language_server
28122        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
28123            let requests_made = Arc::clone(&closure_requests_made);
28124            async move {
28125                assert_eq!(
28126                    params.text_document.uri,
28127                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
28128                );
28129                requests_made.fetch_add(1, atomic::Ordering::Release);
28130                Ok(Vec::new())
28131            }
28132        });
28133    let save = editor.update_in(cx, |editor, window, cx| {
28134        editor.move_to_end(&MoveToEnd, window, cx);
28135        editor.handle_input("dirty_again", window, cx);
28136        editor.save(
28137            SaveOptions {
28138                format: false,
28139                autosave: true,
28140            },
28141            project.clone(),
28142            window,
28143            cx,
28144        )
28145    });
28146    save.await.unwrap();
28147
28148    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
28149    empty_color_request_handle.next().await.unwrap();
28150    cx.run_until_parked();
28151    assert_eq!(
28152        3,
28153        requests_made.load(atomic::Ordering::Acquire),
28154        "Should query for colors once per save only, as formatting was not requested"
28155    );
28156    editor.update(cx, |editor, cx| {
28157        assert_eq!(
28158            Vec::<Rgba>::new(),
28159            extract_color_inlays(editor, cx),
28160            "Should clear all colors when the server returns an empty response"
28161        );
28162    });
28163}
28164
28165#[gpui::test]
28166async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
28167    init_test(cx, |_| {});
28168    let (editor, cx) = cx.add_window_view(Editor::single_line);
28169    editor.update_in(cx, |editor, window, cx| {
28170        editor.set_text("oops\n\nwow\n", window, cx)
28171    });
28172    cx.run_until_parked();
28173    editor.update(cx, |editor, cx| {
28174        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
28175    });
28176    editor.update(cx, |editor, cx| {
28177        editor.edit([(MultiBufferOffset(3)..MultiBufferOffset(5), "")], cx)
28178    });
28179    cx.run_until_parked();
28180    editor.update(cx, |editor, cx| {
28181        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
28182    });
28183}
28184
28185#[gpui::test]
28186async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
28187    init_test(cx, |_| {});
28188
28189    cx.update(|cx| {
28190        register_project_item::<Editor>(cx);
28191    });
28192
28193    let fs = FakeFs::new(cx.executor());
28194    fs.insert_tree("/root1", json!({})).await;
28195    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
28196        .await;
28197
28198    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
28199    let (workspace, cx) =
28200        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
28201
28202    let worktree_id = project.update(cx, |project, cx| {
28203        project.worktrees(cx).next().unwrap().read(cx).id()
28204    });
28205
28206    let handle = workspace
28207        .update_in(cx, |workspace, window, cx| {
28208            let project_path = (worktree_id, rel_path("one.pdf"));
28209            workspace.open_path(project_path, None, true, window, cx)
28210        })
28211        .await
28212        .unwrap();
28213    // The test file content `vec![0xff, 0xfe, ...]` starts with a UTF-16 LE BOM.
28214    // Previously, this fell back to `InvalidItemView` because it wasn't valid UTF-8.
28215    // With auto-detection enabled, this is now recognized as UTF-16 and opens in the Editor.
28216    assert_eq!(handle.to_any_view().entity_type(), TypeId::of::<Editor>());
28217}
28218
28219#[gpui::test]
28220async fn test_select_next_prev_syntax_node(cx: &mut TestAppContext) {
28221    init_test(cx, |_| {});
28222
28223    let language = Arc::new(Language::new(
28224        LanguageConfig::default(),
28225        Some(tree_sitter_rust::LANGUAGE.into()),
28226    ));
28227
28228    // Test hierarchical sibling navigation
28229    let text = r#"
28230        fn outer() {
28231            if condition {
28232                let a = 1;
28233            }
28234            let b = 2;
28235        }
28236
28237        fn another() {
28238            let c = 3;
28239        }
28240    "#;
28241
28242    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
28243    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
28244    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
28245
28246    // Wait for parsing to complete
28247    editor
28248        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
28249        .await;
28250
28251    editor.update_in(cx, |editor, window, cx| {
28252        // Start by selecting "let a = 1;" inside the if block
28253        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28254            s.select_display_ranges([
28255                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 26)
28256            ]);
28257        });
28258
28259        let initial_selection = editor
28260            .selections
28261            .display_ranges(&editor.display_snapshot(cx));
28262        assert_eq!(initial_selection.len(), 1, "Should have one selection");
28263
28264        // Test select next sibling - should move up levels to find the next sibling
28265        // Since "let a = 1;" has no siblings in the if block, it should move up
28266        // to find "let b = 2;" which is a sibling of the if block
28267        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
28268        let next_selection = editor
28269            .selections
28270            .display_ranges(&editor.display_snapshot(cx));
28271
28272        // Should have a selection and it should be different from the initial
28273        assert_eq!(
28274            next_selection.len(),
28275            1,
28276            "Should have one selection after next"
28277        );
28278        assert_ne!(
28279            next_selection[0], initial_selection[0],
28280            "Next sibling selection should be different"
28281        );
28282
28283        // Test hierarchical navigation by going to the end of the current function
28284        // and trying to navigate to the next function
28285        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28286            s.select_display_ranges([
28287                DisplayPoint::new(DisplayRow(5), 12)..DisplayPoint::new(DisplayRow(5), 22)
28288            ]);
28289        });
28290
28291        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
28292        let function_next_selection = editor
28293            .selections
28294            .display_ranges(&editor.display_snapshot(cx));
28295
28296        // Should move to the next function
28297        assert_eq!(
28298            function_next_selection.len(),
28299            1,
28300            "Should have one selection after function next"
28301        );
28302
28303        // Test select previous sibling navigation
28304        editor.select_prev_syntax_node(&SelectPreviousSyntaxNode, window, cx);
28305        let prev_selection = editor
28306            .selections
28307            .display_ranges(&editor.display_snapshot(cx));
28308
28309        // Should have a selection and it should be different
28310        assert_eq!(
28311            prev_selection.len(),
28312            1,
28313            "Should have one selection after prev"
28314        );
28315        assert_ne!(
28316            prev_selection[0], function_next_selection[0],
28317            "Previous sibling selection should be different from next"
28318        );
28319    });
28320}
28321
28322#[gpui::test]
28323async fn test_next_prev_document_highlight(cx: &mut TestAppContext) {
28324    init_test(cx, |_| {});
28325
28326    let mut cx = EditorTestContext::new(cx).await;
28327    cx.set_state(
28328        "let ˇvariable = 42;
28329let another = variable + 1;
28330let result = variable * 2;",
28331    );
28332
28333    // Set up document highlights manually (simulating LSP response)
28334    cx.update_editor(|editor, _window, cx| {
28335        let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
28336
28337        // Create highlights for "variable" occurrences
28338        let highlight_ranges = [
28339            Point::new(0, 4)..Point::new(0, 12),  // First "variable"
28340            Point::new(1, 14)..Point::new(1, 22), // Second "variable"
28341            Point::new(2, 13)..Point::new(2, 21), // Third "variable"
28342        ];
28343
28344        let anchor_ranges: Vec<_> = highlight_ranges
28345            .iter()
28346            .map(|range| range.clone().to_anchors(&buffer_snapshot))
28347            .collect();
28348
28349        editor.highlight_background::<DocumentHighlightRead>(
28350            &anchor_ranges,
28351            |_, theme| theme.colors().editor_document_highlight_read_background,
28352            cx,
28353        );
28354    });
28355
28356    // Go to next highlight - should move to second "variable"
28357    cx.update_editor(|editor, window, cx| {
28358        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
28359    });
28360    cx.assert_editor_state(
28361        "let variable = 42;
28362let another = ˇvariable + 1;
28363let result = variable * 2;",
28364    );
28365
28366    // Go to next highlight - should move to third "variable"
28367    cx.update_editor(|editor, window, cx| {
28368        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
28369    });
28370    cx.assert_editor_state(
28371        "let variable = 42;
28372let another = variable + 1;
28373let result = ˇvariable * 2;",
28374    );
28375
28376    // Go to next highlight - should stay at third "variable" (no wrap-around)
28377    cx.update_editor(|editor, window, cx| {
28378        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
28379    });
28380    cx.assert_editor_state(
28381        "let variable = 42;
28382let another = variable + 1;
28383let result = ˇvariable * 2;",
28384    );
28385
28386    // Now test going backwards from third position
28387    cx.update_editor(|editor, window, cx| {
28388        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
28389    });
28390    cx.assert_editor_state(
28391        "let variable = 42;
28392let another = ˇvariable + 1;
28393let result = variable * 2;",
28394    );
28395
28396    // Go to previous highlight - should move to first "variable"
28397    cx.update_editor(|editor, window, cx| {
28398        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
28399    });
28400    cx.assert_editor_state(
28401        "let ˇvariable = 42;
28402let another = variable + 1;
28403let result = variable * 2;",
28404    );
28405
28406    // Go to previous highlight - should stay on first "variable"
28407    cx.update_editor(|editor, window, cx| {
28408        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
28409    });
28410    cx.assert_editor_state(
28411        "let ˇvariable = 42;
28412let another = variable + 1;
28413let result = variable * 2;",
28414    );
28415}
28416
28417#[gpui::test]
28418async fn test_paste_url_from_other_app_creates_markdown_link_over_selected_text(
28419    cx: &mut gpui::TestAppContext,
28420) {
28421    init_test(cx, |_| {});
28422
28423    let url = "https://zed.dev";
28424
28425    let markdown_language = Arc::new(Language::new(
28426        LanguageConfig {
28427            name: "Markdown".into(),
28428            ..LanguageConfig::default()
28429        },
28430        None,
28431    ));
28432
28433    let mut cx = EditorTestContext::new(cx).await;
28434    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28435    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)");
28436
28437    cx.update_editor(|editor, window, cx| {
28438        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
28439        editor.paste(&Paste, window, cx);
28440    });
28441
28442    cx.assert_editor_state(&format!(
28443        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)"
28444    ));
28445}
28446
28447#[gpui::test]
28448async fn test_markdown_indents(cx: &mut gpui::TestAppContext) {
28449    init_test(cx, |_| {});
28450
28451    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
28452    let mut cx = EditorTestContext::new(cx).await;
28453
28454    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28455
28456    // Case 1: Test if adding a character with multi cursors preserves nested list indents
28457    cx.set_state(&indoc! {"
28458        - [ ] Item 1
28459            - [ ] Item 1.a
28460        - [ˇ] Item 2
28461            - [ˇ] Item 2.a
28462            - [ˇ] Item 2.b
28463        "
28464    });
28465    cx.update_editor(|editor, window, cx| {
28466        editor.handle_input("x", window, cx);
28467    });
28468    cx.run_until_parked();
28469    cx.assert_editor_state(indoc! {"
28470        - [ ] Item 1
28471            - [ ] Item 1.a
28472        - [xˇ] Item 2
28473            - [xˇ] Item 2.a
28474            - [xˇ] Item 2.b
28475        "
28476    });
28477
28478    // Case 2: Test adding new line after nested list continues the list with unchecked task
28479    cx.set_state(&indoc! {"
28480        - [ ] Item 1
28481            - [ ] Item 1.a
28482        - [x] Item 2
28483            - [x] Item 2.a
28484            - [x] Item 2.bˇ"
28485    });
28486    cx.update_editor(|editor, window, cx| {
28487        editor.newline(&Newline, window, cx);
28488    });
28489    cx.assert_editor_state(indoc! {"
28490        - [ ] Item 1
28491            - [ ] Item 1.a
28492        - [x] Item 2
28493            - [x] Item 2.a
28494            - [x] Item 2.b
28495            - [ ] ˇ"
28496    });
28497
28498    // Case 3: Test adding content to continued list item
28499    cx.update_editor(|editor, window, cx| {
28500        editor.handle_input("Item 2.c", window, cx);
28501    });
28502    cx.run_until_parked();
28503    cx.assert_editor_state(indoc! {"
28504        - [ ] Item 1
28505            - [ ] Item 1.a
28506        - [x] Item 2
28507            - [x] Item 2.a
28508            - [x] Item 2.b
28509            - [ ] Item 2.cˇ"
28510    });
28511
28512    // Case 4: Test adding new line after nested ordered list continues with next number
28513    cx.set_state(indoc! {"
28514        1. Item 1
28515            1. Item 1.a
28516        2. Item 2
28517            1. Item 2.a
28518            2. Item 2.bˇ"
28519    });
28520    cx.update_editor(|editor, window, cx| {
28521        editor.newline(&Newline, window, cx);
28522    });
28523    cx.assert_editor_state(indoc! {"
28524        1. Item 1
28525            1. Item 1.a
28526        2. Item 2
28527            1. Item 2.a
28528            2. Item 2.b
28529            3. ˇ"
28530    });
28531
28532    // Case 5: Adding content to continued ordered list item
28533    cx.update_editor(|editor, window, cx| {
28534        editor.handle_input("Item 2.c", window, cx);
28535    });
28536    cx.run_until_parked();
28537    cx.assert_editor_state(indoc! {"
28538        1. Item 1
28539            1. Item 1.a
28540        2. Item 2
28541            1. Item 2.a
28542            2. Item 2.b
28543            3. Item 2.cˇ"
28544    });
28545
28546    // Case 6: Test adding new line after nested ordered list preserves indent of previous line
28547    cx.set_state(indoc! {"
28548        - Item 1
28549            - Item 1.a
28550            - Item 1.a
28551        ˇ"});
28552    cx.update_editor(|editor, window, cx| {
28553        editor.handle_input("-", window, cx);
28554    });
28555    cx.run_until_parked();
28556    cx.assert_editor_state(indoc! {"
28557        - Item 1
28558            - Item 1.a
28559            - Item 1.a
28560"});
28561
28562    // Case 7: Test blockquote newline preserves something
28563    cx.set_state(indoc! {"
28564        > Item 1ˇ"
28565    });
28566    cx.update_editor(|editor, window, cx| {
28567        editor.newline(&Newline, window, cx);
28568    });
28569    cx.assert_editor_state(indoc! {"
28570        > Item 1
28571        ˇ"
28572    });
28573}
28574
28575#[gpui::test]
28576async fn test_paste_url_from_zed_copy_creates_markdown_link_over_selected_text(
28577    cx: &mut gpui::TestAppContext,
28578) {
28579    init_test(cx, |_| {});
28580
28581    let url = "https://zed.dev";
28582
28583    let markdown_language = Arc::new(Language::new(
28584        LanguageConfig {
28585            name: "Markdown".into(),
28586            ..LanguageConfig::default()
28587        },
28588        None,
28589    ));
28590
28591    let mut cx = EditorTestContext::new(cx).await;
28592    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28593    cx.set_state(&format!(
28594        "Hello, editor.\nZed is great (see this link: )\n«{url}ˇ»"
28595    ));
28596
28597    cx.update_editor(|editor, window, cx| {
28598        editor.copy(&Copy, window, cx);
28599    });
28600
28601    cx.set_state(&format!(
28602        "Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)\n{url}"
28603    ));
28604
28605    cx.update_editor(|editor, window, cx| {
28606        editor.paste(&Paste, window, cx);
28607    });
28608
28609    cx.assert_editor_state(&format!(
28610        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)\n{url}"
28611    ));
28612}
28613
28614#[gpui::test]
28615async fn test_paste_url_from_other_app_replaces_existing_url_without_creating_markdown_link(
28616    cx: &mut gpui::TestAppContext,
28617) {
28618    init_test(cx, |_| {});
28619
28620    let url = "https://zed.dev";
28621
28622    let markdown_language = Arc::new(Language::new(
28623        LanguageConfig {
28624            name: "Markdown".into(),
28625            ..LanguageConfig::default()
28626        },
28627        None,
28628    ));
28629
28630    let mut cx = EditorTestContext::new(cx).await;
28631    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28632    cx.set_state("Please visit zed's homepage: «https://www.apple.comˇ»");
28633
28634    cx.update_editor(|editor, window, cx| {
28635        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
28636        editor.paste(&Paste, window, cx);
28637    });
28638
28639    cx.assert_editor_state(&format!("Please visit zed's homepage: {url}ˇ"));
28640}
28641
28642#[gpui::test]
28643async fn test_paste_plain_text_from_other_app_replaces_selection_without_creating_markdown_link(
28644    cx: &mut gpui::TestAppContext,
28645) {
28646    init_test(cx, |_| {});
28647
28648    let text = "Awesome";
28649
28650    let markdown_language = Arc::new(Language::new(
28651        LanguageConfig {
28652            name: "Markdown".into(),
28653            ..LanguageConfig::default()
28654        },
28655        None,
28656    ));
28657
28658    let mut cx = EditorTestContext::new(cx).await;
28659    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28660    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat»");
28661
28662    cx.update_editor(|editor, window, cx| {
28663        cx.write_to_clipboard(ClipboardItem::new_string(text.to_string()));
28664        editor.paste(&Paste, window, cx);
28665    });
28666
28667    cx.assert_editor_state(&format!("Hello, {text}ˇ.\nZed is {text}ˇ"));
28668}
28669
28670#[gpui::test]
28671async fn test_paste_url_from_other_app_without_creating_markdown_link_in_non_markdown_language(
28672    cx: &mut gpui::TestAppContext,
28673) {
28674    init_test(cx, |_| {});
28675
28676    let url = "https://zed.dev";
28677
28678    let markdown_language = Arc::new(Language::new(
28679        LanguageConfig {
28680            name: "Rust".into(),
28681            ..LanguageConfig::default()
28682        },
28683        None,
28684    ));
28685
28686    let mut cx = EditorTestContext::new(cx).await;
28687    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28688    cx.set_state("// Hello, «editorˇ».\n// Zed is «ˇgreat» (see this link: ˇ)");
28689
28690    cx.update_editor(|editor, window, cx| {
28691        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
28692        editor.paste(&Paste, window, cx);
28693    });
28694
28695    cx.assert_editor_state(&format!(
28696        "// Hello, {url}ˇ.\n// Zed is {url}ˇ (see this link: {url}ˇ)"
28697    ));
28698}
28699
28700#[gpui::test]
28701async fn test_paste_url_from_other_app_creates_markdown_link_selectively_in_multi_buffer(
28702    cx: &mut TestAppContext,
28703) {
28704    init_test(cx, |_| {});
28705
28706    let url = "https://zed.dev";
28707
28708    let markdown_language = Arc::new(Language::new(
28709        LanguageConfig {
28710            name: "Markdown".into(),
28711            ..LanguageConfig::default()
28712        },
28713        None,
28714    ));
28715
28716    let (editor, cx) = cx.add_window_view(|window, cx| {
28717        let multi_buffer = MultiBuffer::build_multi(
28718            [
28719                ("this will embed -> link", vec![Point::row_range(0..1)]),
28720                ("this will replace -> link", vec![Point::row_range(0..1)]),
28721            ],
28722            cx,
28723        );
28724        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
28725        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28726            s.select_ranges(vec![
28727                Point::new(0, 19)..Point::new(0, 23),
28728                Point::new(1, 21)..Point::new(1, 25),
28729            ])
28730        });
28731        let first_buffer_id = multi_buffer
28732            .read(cx)
28733            .excerpt_buffer_ids()
28734            .into_iter()
28735            .next()
28736            .unwrap();
28737        let first_buffer = multi_buffer.read(cx).buffer(first_buffer_id).unwrap();
28738        first_buffer.update(cx, |buffer, cx| {
28739            buffer.set_language(Some(markdown_language.clone()), cx);
28740        });
28741
28742        editor
28743    });
28744    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
28745
28746    cx.update_editor(|editor, window, cx| {
28747        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
28748        editor.paste(&Paste, window, cx);
28749    });
28750
28751    cx.assert_editor_state(&format!(
28752        "this will embed -> [link]({url}\nthis will replace -> {url}ˇ"
28753    ));
28754}
28755
28756#[gpui::test]
28757async fn test_race_in_multibuffer_save(cx: &mut TestAppContext) {
28758    init_test(cx, |_| {});
28759
28760    let fs = FakeFs::new(cx.executor());
28761    fs.insert_tree(
28762        path!("/project"),
28763        json!({
28764            "first.rs": "# First Document\nSome content here.",
28765            "second.rs": "Plain text content for second file.",
28766        }),
28767    )
28768    .await;
28769
28770    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
28771    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
28772    let cx = &mut VisualTestContext::from_window(*workspace, cx);
28773
28774    let language = rust_lang();
28775    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
28776    language_registry.add(language.clone());
28777    let mut fake_servers = language_registry.register_fake_lsp(
28778        "Rust",
28779        FakeLspAdapter {
28780            ..FakeLspAdapter::default()
28781        },
28782    );
28783
28784    let buffer1 = project
28785        .update(cx, |project, cx| {
28786            project.open_local_buffer(PathBuf::from(path!("/project/first.rs")), cx)
28787        })
28788        .await
28789        .unwrap();
28790    let buffer2 = project
28791        .update(cx, |project, cx| {
28792            project.open_local_buffer(PathBuf::from(path!("/project/second.rs")), cx)
28793        })
28794        .await
28795        .unwrap();
28796
28797    let multi_buffer = cx.new(|cx| {
28798        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
28799        multi_buffer.set_excerpts_for_path(
28800            PathKey::for_buffer(&buffer1, cx),
28801            buffer1.clone(),
28802            [Point::zero()..buffer1.read(cx).max_point()],
28803            3,
28804            cx,
28805        );
28806        multi_buffer.set_excerpts_for_path(
28807            PathKey::for_buffer(&buffer2, cx),
28808            buffer2.clone(),
28809            [Point::zero()..buffer1.read(cx).max_point()],
28810            3,
28811            cx,
28812        );
28813        multi_buffer
28814    });
28815
28816    let (editor, cx) = cx.add_window_view(|window, cx| {
28817        Editor::new(
28818            EditorMode::full(),
28819            multi_buffer,
28820            Some(project.clone()),
28821            window,
28822            cx,
28823        )
28824    });
28825
28826    let fake_language_server = fake_servers.next().await.unwrap();
28827
28828    buffer1.update(cx, |buffer, cx| buffer.edit([(0..0, "hello!")], None, cx));
28829
28830    let save = editor.update_in(cx, |editor, window, cx| {
28831        assert!(editor.is_dirty(cx));
28832
28833        editor.save(
28834            SaveOptions {
28835                format: true,
28836                autosave: true,
28837            },
28838            project,
28839            window,
28840            cx,
28841        )
28842    });
28843    let (start_edit_tx, start_edit_rx) = oneshot::channel();
28844    let (done_edit_tx, done_edit_rx) = oneshot::channel();
28845    let mut done_edit_rx = Some(done_edit_rx);
28846    let mut start_edit_tx = Some(start_edit_tx);
28847
28848    fake_language_server.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| {
28849        start_edit_tx.take().unwrap().send(()).unwrap();
28850        let done_edit_rx = done_edit_rx.take().unwrap();
28851        async move {
28852            done_edit_rx.await.unwrap();
28853            Ok(None)
28854        }
28855    });
28856
28857    start_edit_rx.await.unwrap();
28858    buffer2
28859        .update(cx, |buffer, cx| buffer.edit([(0..0, "world!")], None, cx))
28860        .unwrap();
28861
28862    done_edit_tx.send(()).unwrap();
28863
28864    save.await.unwrap();
28865    cx.update(|_, cx| assert!(editor.is_dirty(cx)));
28866}
28867
28868#[track_caller]
28869fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
28870    editor
28871        .all_inlays(cx)
28872        .into_iter()
28873        .filter_map(|inlay| inlay.get_color())
28874        .map(Rgba::from)
28875        .collect()
28876}
28877
28878#[gpui::test]
28879fn test_duplicate_line_up_on_last_line_without_newline(cx: &mut TestAppContext) {
28880    init_test(cx, |_| {});
28881
28882    let editor = cx.add_window(|window, cx| {
28883        let buffer = MultiBuffer::build_simple("line1\nline2", cx);
28884        build_editor(buffer, window, cx)
28885    });
28886
28887    editor
28888        .update(cx, |editor, window, cx| {
28889            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28890                s.select_display_ranges([
28891                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
28892                ])
28893            });
28894
28895            editor.duplicate_line_up(&DuplicateLineUp, window, cx);
28896
28897            assert_eq!(
28898                editor.display_text(cx),
28899                "line1\nline2\nline2",
28900                "Duplicating last line upward should create duplicate above, not on same line"
28901            );
28902
28903            assert_eq!(
28904                editor
28905                    .selections
28906                    .display_ranges(&editor.display_snapshot(cx)),
28907                vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)],
28908                "Selection should move to the duplicated line"
28909            );
28910        })
28911        .unwrap();
28912}
28913
28914#[gpui::test]
28915async fn test_copy_line_without_trailing_newline(cx: &mut TestAppContext) {
28916    init_test(cx, |_| {});
28917
28918    let mut cx = EditorTestContext::new(cx).await;
28919
28920    cx.set_state("line1\nline2ˇ");
28921
28922    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
28923
28924    let clipboard_text = cx
28925        .read_from_clipboard()
28926        .and_then(|item| item.text().as_deref().map(str::to_string));
28927
28928    assert_eq!(
28929        clipboard_text,
28930        Some("line2\n".to_string()),
28931        "Copying a line without trailing newline should include a newline"
28932    );
28933
28934    cx.set_state("line1\nˇ");
28935
28936    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
28937
28938    cx.assert_editor_state("line1\nline2\nˇ");
28939}
28940
28941#[gpui::test]
28942async fn test_multi_selection_copy_with_newline_between_copied_lines(cx: &mut TestAppContext) {
28943    init_test(cx, |_| {});
28944
28945    let mut cx = EditorTestContext::new(cx).await;
28946
28947    cx.set_state("ˇline1\nˇline2\nˇline3\n");
28948
28949    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
28950
28951    let clipboard_text = cx
28952        .read_from_clipboard()
28953        .and_then(|item| item.text().as_deref().map(str::to_string));
28954
28955    assert_eq!(
28956        clipboard_text,
28957        Some("line1\nline2\nline3\n".to_string()),
28958        "Copying multiple lines should include a single newline between lines"
28959    );
28960
28961    cx.set_state("lineA\nˇ");
28962
28963    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
28964
28965    cx.assert_editor_state("lineA\nline1\nline2\nline3\nˇ");
28966}
28967
28968#[gpui::test]
28969async fn test_multi_selection_cut_with_newline_between_copied_lines(cx: &mut TestAppContext) {
28970    init_test(cx, |_| {});
28971
28972    let mut cx = EditorTestContext::new(cx).await;
28973
28974    cx.set_state("ˇline1\nˇline2\nˇline3\n");
28975
28976    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
28977
28978    let clipboard_text = cx
28979        .read_from_clipboard()
28980        .and_then(|item| item.text().as_deref().map(str::to_string));
28981
28982    assert_eq!(
28983        clipboard_text,
28984        Some("line1\nline2\nline3\n".to_string()),
28985        "Copying multiple lines should include a single newline between lines"
28986    );
28987
28988    cx.set_state("lineA\nˇ");
28989
28990    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
28991
28992    cx.assert_editor_state("lineA\nline1\nline2\nline3\nˇ");
28993}
28994
28995#[gpui::test]
28996async fn test_end_of_editor_context(cx: &mut TestAppContext) {
28997    init_test(cx, |_| {});
28998
28999    let mut cx = EditorTestContext::new(cx).await;
29000
29001    cx.set_state("line1\nline2ˇ");
29002    cx.update_editor(|e, window, cx| {
29003        e.set_mode(EditorMode::SingleLine);
29004        assert!(e.key_context(window, cx).contains("end_of_input"));
29005    });
29006    cx.set_state("ˇline1\nline2");
29007    cx.update_editor(|e, window, cx| {
29008        assert!(!e.key_context(window, cx).contains("end_of_input"));
29009    });
29010    cx.set_state("line1ˇ\nline2");
29011    cx.update_editor(|e, window, cx| {
29012        assert!(!e.key_context(window, cx).contains("end_of_input"));
29013    });
29014}
29015
29016#[gpui::test]
29017async fn test_sticky_scroll(cx: &mut TestAppContext) {
29018    init_test(cx, |_| {});
29019    let mut cx = EditorTestContext::new(cx).await;
29020
29021    let buffer = indoc! {"
29022            ˇfn foo() {
29023                let abc = 123;
29024            }
29025            struct Bar;
29026            impl Bar {
29027                fn new() -> Self {
29028                    Self
29029                }
29030            }
29031            fn baz() {
29032            }
29033        "};
29034    cx.set_state(&buffer);
29035
29036    cx.update_editor(|e, _, cx| {
29037        e.buffer()
29038            .read(cx)
29039            .as_singleton()
29040            .unwrap()
29041            .update(cx, |buffer, cx| {
29042                buffer.set_language(Some(rust_lang()), cx);
29043            })
29044    });
29045
29046    let mut sticky_headers = |offset: ScrollOffset| {
29047        cx.update_editor(|e, window, cx| {
29048            e.scroll(gpui::Point { x: 0., y: offset }, None, window, cx);
29049            let style = e.style(cx).clone();
29050            EditorElement::sticky_headers(&e, &e.snapshot(window, cx), &style, cx)
29051                .into_iter()
29052                .map(
29053                    |StickyHeader {
29054                         start_point,
29055                         offset,
29056                         ..
29057                     }| { (start_point, offset) },
29058                )
29059                .collect::<Vec<_>>()
29060        })
29061    };
29062
29063    let fn_foo = Point { row: 0, column: 0 };
29064    let impl_bar = Point { row: 4, column: 0 };
29065    let fn_new = Point { row: 5, column: 4 };
29066
29067    assert_eq!(sticky_headers(0.0), vec![]);
29068    assert_eq!(sticky_headers(0.5), vec![(fn_foo, 0.0)]);
29069    assert_eq!(sticky_headers(1.0), vec![(fn_foo, 0.0)]);
29070    assert_eq!(sticky_headers(1.5), vec![(fn_foo, -0.5)]);
29071    assert_eq!(sticky_headers(2.0), vec![]);
29072    assert_eq!(sticky_headers(2.5), vec![]);
29073    assert_eq!(sticky_headers(3.0), vec![]);
29074    assert_eq!(sticky_headers(3.5), vec![]);
29075    assert_eq!(sticky_headers(4.0), vec![]);
29076    assert_eq!(sticky_headers(4.5), vec![(impl_bar, 0.0), (fn_new, 1.0)]);
29077    assert_eq!(sticky_headers(5.0), vec![(impl_bar, 0.0), (fn_new, 1.0)]);
29078    assert_eq!(sticky_headers(5.5), vec![(impl_bar, 0.0), (fn_new, 0.5)]);
29079    assert_eq!(sticky_headers(6.0), vec![(impl_bar, 0.0)]);
29080    assert_eq!(sticky_headers(6.5), vec![(impl_bar, 0.0)]);
29081    assert_eq!(sticky_headers(7.0), vec![(impl_bar, 0.0)]);
29082    assert_eq!(sticky_headers(7.5), vec![(impl_bar, -0.5)]);
29083    assert_eq!(sticky_headers(8.0), vec![]);
29084    assert_eq!(sticky_headers(8.5), vec![]);
29085    assert_eq!(sticky_headers(9.0), vec![]);
29086    assert_eq!(sticky_headers(9.5), vec![]);
29087    assert_eq!(sticky_headers(10.0), vec![]);
29088}
29089
29090#[gpui::test]
29091fn test_relative_line_numbers(cx: &mut TestAppContext) {
29092    init_test(cx, |_| {});
29093
29094    let buffer_1 = cx.new(|cx| Buffer::local("aaaaaaaaaa\nbbb\n", cx));
29095    let buffer_2 = cx.new(|cx| Buffer::local("cccccccccc\nddd\n", cx));
29096    let buffer_3 = cx.new(|cx| Buffer::local("eee\nffffffffff\n", cx));
29097
29098    let multibuffer = cx.new(|cx| {
29099        let mut multibuffer = MultiBuffer::new(ReadWrite);
29100        multibuffer.push_excerpts(
29101            buffer_1.clone(),
29102            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
29103            cx,
29104        );
29105        multibuffer.push_excerpts(
29106            buffer_2.clone(),
29107            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
29108            cx,
29109        );
29110        multibuffer.push_excerpts(
29111            buffer_3.clone(),
29112            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
29113            cx,
29114        );
29115        multibuffer
29116    });
29117
29118    // wrapped contents of multibuffer:
29119    //    aaa
29120    //    aaa
29121    //    aaa
29122    //    a
29123    //    bbb
29124    //
29125    //    ccc
29126    //    ccc
29127    //    ccc
29128    //    c
29129    //    ddd
29130    //
29131    //    eee
29132    //    fff
29133    //    fff
29134    //    fff
29135    //    f
29136
29137    let editor = cx.add_window(|window, cx| build_editor(multibuffer, window, cx));
29138    _ = editor.update(cx, |editor, window, cx| {
29139        editor.set_wrap_width(Some(30.0.into()), cx); // every 3 characters
29140
29141        // includes trailing newlines.
29142        let expected_line_numbers = [2, 6, 7, 10, 14, 15, 18, 19, 23];
29143        let expected_wrapped_line_numbers = [
29144            2, 3, 4, 5, 6, 7, 10, 11, 12, 13, 14, 15, 18, 19, 20, 21, 22, 23,
29145        ];
29146
29147        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
29148            s.select_ranges([
29149                Point::new(7, 0)..Point::new(7, 1), // second row of `ccc`
29150            ]);
29151        });
29152
29153        let snapshot = editor.snapshot(window, cx);
29154
29155        // these are all 0-indexed
29156        let base_display_row = DisplayRow(11);
29157        let base_row = 3;
29158        let wrapped_base_row = 7;
29159
29160        // test not counting wrapped lines
29161        let expected_relative_numbers = expected_line_numbers
29162            .into_iter()
29163            .enumerate()
29164            .map(|(i, row)| (DisplayRow(row), i.abs_diff(base_row) as u32))
29165            .collect_vec();
29166        let actual_relative_numbers = snapshot
29167            .calculate_relative_line_numbers(
29168                &(DisplayRow(0)..DisplayRow(24)),
29169                base_display_row,
29170                false,
29171            )
29172            .into_iter()
29173            .sorted()
29174            .collect_vec();
29175        assert_eq!(expected_relative_numbers, actual_relative_numbers);
29176        // check `calculate_relative_line_numbers()` against `relative_line_delta()` for each line
29177        for (display_row, relative_number) in expected_relative_numbers {
29178            assert_eq!(
29179                relative_number,
29180                snapshot
29181                    .relative_line_delta(display_row, base_display_row, false)
29182                    .unsigned_abs() as u32,
29183            );
29184        }
29185
29186        // test counting wrapped lines
29187        let expected_wrapped_relative_numbers = expected_wrapped_line_numbers
29188            .into_iter()
29189            .enumerate()
29190            .map(|(i, row)| (DisplayRow(row), i.abs_diff(wrapped_base_row) as u32))
29191            .filter(|(row, _)| *row != base_display_row)
29192            .collect_vec();
29193        let actual_relative_numbers = snapshot
29194            .calculate_relative_line_numbers(
29195                &(DisplayRow(0)..DisplayRow(24)),
29196                base_display_row,
29197                true,
29198            )
29199            .into_iter()
29200            .sorted()
29201            .collect_vec();
29202        assert_eq!(expected_wrapped_relative_numbers, actual_relative_numbers);
29203        // check `calculate_relative_line_numbers()` against `relative_wrapped_line_delta()` for each line
29204        for (display_row, relative_number) in expected_wrapped_relative_numbers {
29205            assert_eq!(
29206                relative_number,
29207                snapshot
29208                    .relative_line_delta(display_row, base_display_row, true)
29209                    .unsigned_abs() as u32,
29210            );
29211        }
29212    });
29213}
29214
29215#[gpui::test]
29216async fn test_scroll_by_clicking_sticky_header(cx: &mut TestAppContext) {
29217    init_test(cx, |_| {});
29218    cx.update(|cx| {
29219        SettingsStore::update_global(cx, |store, cx| {
29220            store.update_user_settings(cx, |settings| {
29221                settings.editor.sticky_scroll = Some(settings::StickyScrollContent {
29222                    enabled: Some(true),
29223                })
29224            });
29225        });
29226    });
29227    let mut cx = EditorTestContext::new(cx).await;
29228
29229    let line_height = cx.update_editor(|editor, window, cx| {
29230        editor
29231            .style(cx)
29232            .text
29233            .line_height_in_pixels(window.rem_size())
29234    });
29235
29236    let buffer = indoc! {"
29237            ˇfn foo() {
29238                let abc = 123;
29239            }
29240            struct Bar;
29241            impl Bar {
29242                fn new() -> Self {
29243                    Self
29244                }
29245            }
29246            fn baz() {
29247            }
29248        "};
29249    cx.set_state(&buffer);
29250
29251    cx.update_editor(|e, _, cx| {
29252        e.buffer()
29253            .read(cx)
29254            .as_singleton()
29255            .unwrap()
29256            .update(cx, |buffer, cx| {
29257                buffer.set_language(Some(rust_lang()), cx);
29258            })
29259    });
29260
29261    let fn_foo = || empty_range(0, 0);
29262    let impl_bar = || empty_range(4, 0);
29263    let fn_new = || empty_range(5, 4);
29264
29265    let mut scroll_and_click = |scroll_offset: ScrollOffset, click_offset: ScrollOffset| {
29266        cx.update_editor(|e, window, cx| {
29267            e.scroll(
29268                gpui::Point {
29269                    x: 0.,
29270                    y: scroll_offset,
29271                },
29272                None,
29273                window,
29274                cx,
29275            );
29276        });
29277        cx.simulate_click(
29278            gpui::Point {
29279                x: px(0.),
29280                y: click_offset as f32 * line_height,
29281            },
29282            Modifiers::none(),
29283        );
29284        cx.update_editor(|e, _, cx| (e.scroll_position(cx), display_ranges(e, cx)))
29285    };
29286
29287    assert_eq!(
29288        scroll_and_click(
29289            4.5, // impl Bar is halfway off the screen
29290            0.0  // click top of screen
29291        ),
29292        // scrolled to impl Bar
29293        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
29294    );
29295
29296    assert_eq!(
29297        scroll_and_click(
29298            4.5,  // impl Bar is halfway off the screen
29299            0.25  // click middle of impl Bar
29300        ),
29301        // scrolled to impl Bar
29302        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
29303    );
29304
29305    assert_eq!(
29306        scroll_and_click(
29307            4.5, // impl Bar is halfway off the screen
29308            1.5  // click below impl Bar (e.g. fn new())
29309        ),
29310        // scrolled to fn new() - this is below the impl Bar header which has persisted
29311        (gpui::Point { x: 0., y: 4. }, vec![fn_new()])
29312    );
29313
29314    assert_eq!(
29315        scroll_and_click(
29316            5.5,  // fn new is halfway underneath impl Bar
29317            0.75  // click on the overlap of impl Bar and fn new()
29318        ),
29319        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
29320    );
29321
29322    assert_eq!(
29323        scroll_and_click(
29324            5.5,  // fn new is halfway underneath impl Bar
29325            1.25  // click on the visible part of fn new()
29326        ),
29327        (gpui::Point { x: 0., y: 4. }, vec![fn_new()])
29328    );
29329
29330    assert_eq!(
29331        scroll_and_click(
29332            1.5, // fn foo is halfway off the screen
29333            0.0  // click top of screen
29334        ),
29335        (gpui::Point { x: 0., y: 0. }, vec![fn_foo()])
29336    );
29337
29338    assert_eq!(
29339        scroll_and_click(
29340            1.5,  // fn foo is halfway off the screen
29341            0.75  // click visible part of let abc...
29342        )
29343        .0,
29344        // no change in scroll
29345        // we don't assert on the visible_range because if we clicked the gutter, our line is fully selected
29346        (gpui::Point { x: 0., y: 1.5 })
29347    );
29348}
29349
29350#[gpui::test]
29351async fn test_next_prev_reference(cx: &mut TestAppContext) {
29352    const CYCLE_POSITIONS: &[&'static str] = &[
29353        indoc! {"
29354            fn foo() {
29355                let ˇabc = 123;
29356                let x = abc + 1;
29357                let y = abc + 2;
29358                let z = abc + 2;
29359            }
29360        "},
29361        indoc! {"
29362            fn foo() {
29363                let abc = 123;
29364                let x = ˇabc + 1;
29365                let y = abc + 2;
29366                let z = abc + 2;
29367            }
29368        "},
29369        indoc! {"
29370            fn foo() {
29371                let abc = 123;
29372                let x = abc + 1;
29373                let y = ˇabc + 2;
29374                let z = abc + 2;
29375            }
29376        "},
29377        indoc! {"
29378            fn foo() {
29379                let abc = 123;
29380                let x = abc + 1;
29381                let y = abc + 2;
29382                let z = ˇabc + 2;
29383            }
29384        "},
29385    ];
29386
29387    init_test(cx, |_| {});
29388
29389    let mut cx = EditorLspTestContext::new_rust(
29390        lsp::ServerCapabilities {
29391            references_provider: Some(lsp::OneOf::Left(true)),
29392            ..Default::default()
29393        },
29394        cx,
29395    )
29396    .await;
29397
29398    // importantly, the cursor is in the middle
29399    cx.set_state(indoc! {"
29400        fn foo() {
29401            let aˇbc = 123;
29402            let x = abc + 1;
29403            let y = abc + 2;
29404            let z = abc + 2;
29405        }
29406    "});
29407
29408    let reference_ranges = [
29409        lsp::Position::new(1, 8),
29410        lsp::Position::new(2, 12),
29411        lsp::Position::new(3, 12),
29412        lsp::Position::new(4, 12),
29413    ]
29414    .map(|start| lsp::Range::new(start, lsp::Position::new(start.line, start.character + 3)));
29415
29416    cx.lsp
29417        .set_request_handler::<lsp::request::References, _, _>(move |params, _cx| async move {
29418            Ok(Some(
29419                reference_ranges
29420                    .map(|range| lsp::Location {
29421                        uri: params.text_document_position.text_document.uri.clone(),
29422                        range,
29423                    })
29424                    .to_vec(),
29425            ))
29426        });
29427
29428    let _move = async |direction, count, cx: &mut EditorLspTestContext| {
29429        cx.update_editor(|editor, window, cx| {
29430            editor.go_to_reference_before_or_after_position(direction, count, window, cx)
29431        })
29432        .unwrap()
29433        .await
29434        .unwrap()
29435    };
29436
29437    _move(Direction::Next, 1, &mut cx).await;
29438    cx.assert_editor_state(CYCLE_POSITIONS[1]);
29439
29440    _move(Direction::Next, 1, &mut cx).await;
29441    cx.assert_editor_state(CYCLE_POSITIONS[2]);
29442
29443    _move(Direction::Next, 1, &mut cx).await;
29444    cx.assert_editor_state(CYCLE_POSITIONS[3]);
29445
29446    // loops back to the start
29447    _move(Direction::Next, 1, &mut cx).await;
29448    cx.assert_editor_state(CYCLE_POSITIONS[0]);
29449
29450    // loops back to the end
29451    _move(Direction::Prev, 1, &mut cx).await;
29452    cx.assert_editor_state(CYCLE_POSITIONS[3]);
29453
29454    _move(Direction::Prev, 1, &mut cx).await;
29455    cx.assert_editor_state(CYCLE_POSITIONS[2]);
29456
29457    _move(Direction::Prev, 1, &mut cx).await;
29458    cx.assert_editor_state(CYCLE_POSITIONS[1]);
29459
29460    _move(Direction::Prev, 1, &mut cx).await;
29461    cx.assert_editor_state(CYCLE_POSITIONS[0]);
29462
29463    _move(Direction::Next, 3, &mut cx).await;
29464    cx.assert_editor_state(CYCLE_POSITIONS[3]);
29465
29466    _move(Direction::Prev, 2, &mut cx).await;
29467    cx.assert_editor_state(CYCLE_POSITIONS[1]);
29468}
29469
29470#[gpui::test]
29471async fn test_multibuffer_selections_with_folding(cx: &mut TestAppContext) {
29472    init_test(cx, |_| {});
29473
29474    let (editor, cx) = cx.add_window_view(|window, cx| {
29475        let multi_buffer = MultiBuffer::build_multi(
29476            [
29477                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
29478                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
29479            ],
29480            cx,
29481        );
29482        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
29483    });
29484
29485    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
29486    let buffer_ids = cx.multibuffer(|mb, _| mb.excerpt_buffer_ids());
29487
29488    cx.assert_excerpts_with_selections(indoc! {"
29489        [EXCERPT]
29490        ˇ1
29491        2
29492        3
29493        [EXCERPT]
29494        1
29495        2
29496        3
29497        "});
29498
29499    // Scenario 1: Unfolded buffers, position cursor on "2", select all matches, then insert
29500    cx.update_editor(|editor, window, cx| {
29501        editor.change_selections(None.into(), window, cx, |s| {
29502            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
29503        });
29504    });
29505    cx.assert_excerpts_with_selections(indoc! {"
29506        [EXCERPT]
29507        1
2950829509        3
29510        [EXCERPT]
29511        1
29512        2
29513        3
29514        "});
29515
29516    cx.update_editor(|editor, window, cx| {
29517        editor
29518            .select_all_matches(&SelectAllMatches, window, cx)
29519            .unwrap();
29520    });
29521    cx.assert_excerpts_with_selections(indoc! {"
29522        [EXCERPT]
29523        1
2952429525        3
29526        [EXCERPT]
29527        1
2952829529        3
29530        "});
29531
29532    cx.update_editor(|editor, window, cx| {
29533        editor.handle_input("X", window, cx);
29534    });
29535    cx.assert_excerpts_with_selections(indoc! {"
29536        [EXCERPT]
29537        1
2953829539        3
29540        [EXCERPT]
29541        1
2954229543        3
29544        "});
29545
29546    // Scenario 2: Select "2", then fold second buffer before insertion
29547    cx.update_multibuffer(|mb, cx| {
29548        for buffer_id in buffer_ids.iter() {
29549            let buffer = mb.buffer(*buffer_id).unwrap();
29550            buffer.update(cx, |buffer, cx| {
29551                buffer.edit([(0..buffer.len(), "1\n2\n3\n")], None, cx);
29552            });
29553        }
29554    });
29555
29556    // Select "2" and select all matches
29557    cx.update_editor(|editor, window, cx| {
29558        editor.change_selections(None.into(), window, cx, |s| {
29559            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
29560        });
29561        editor
29562            .select_all_matches(&SelectAllMatches, window, cx)
29563            .unwrap();
29564    });
29565
29566    // Fold second buffer - should remove selections from folded buffer
29567    cx.update_editor(|editor, _, cx| {
29568        editor.fold_buffer(buffer_ids[1], cx);
29569    });
29570    cx.assert_excerpts_with_selections(indoc! {"
29571        [EXCERPT]
29572        1
2957329574        3
29575        [EXCERPT]
29576        [FOLDED]
29577        "});
29578
29579    // Insert text - should only affect first buffer
29580    cx.update_editor(|editor, window, cx| {
29581        editor.handle_input("Y", window, cx);
29582    });
29583    cx.update_editor(|editor, _, cx| {
29584        editor.unfold_buffer(buffer_ids[1], cx);
29585    });
29586    cx.assert_excerpts_with_selections(indoc! {"
29587        [EXCERPT]
29588        1
2958929590        3
29591        [EXCERPT]
29592        1
29593        2
29594        3
29595        "});
29596
29597    // Scenario 3: Select "2", then fold first buffer before insertion
29598    cx.update_multibuffer(|mb, cx| {
29599        for buffer_id in buffer_ids.iter() {
29600            let buffer = mb.buffer(*buffer_id).unwrap();
29601            buffer.update(cx, |buffer, cx| {
29602                buffer.edit([(0..buffer.len(), "1\n2\n3\n")], None, cx);
29603            });
29604        }
29605    });
29606
29607    // Select "2" and select all matches
29608    cx.update_editor(|editor, window, cx| {
29609        editor.change_selections(None.into(), window, cx, |s| {
29610            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
29611        });
29612        editor
29613            .select_all_matches(&SelectAllMatches, window, cx)
29614            .unwrap();
29615    });
29616
29617    // Fold first buffer - should remove selections from folded buffer
29618    cx.update_editor(|editor, _, cx| {
29619        editor.fold_buffer(buffer_ids[0], cx);
29620    });
29621    cx.assert_excerpts_with_selections(indoc! {"
29622        [EXCERPT]
29623        [FOLDED]
29624        [EXCERPT]
29625        1
2962629627        3
29628        "});
29629
29630    // Insert text - should only affect second buffer
29631    cx.update_editor(|editor, window, cx| {
29632        editor.handle_input("Z", window, cx);
29633    });
29634    cx.update_editor(|editor, _, cx| {
29635        editor.unfold_buffer(buffer_ids[0], cx);
29636    });
29637    cx.assert_excerpts_with_selections(indoc! {"
29638        [EXCERPT]
29639        1
29640        2
29641        3
29642        [EXCERPT]
29643        1
2964429645        3
29646        "});
29647
29648    // Test correct folded header is selected upon fold
29649    cx.update_editor(|editor, _, cx| {
29650        editor.fold_buffer(buffer_ids[0], cx);
29651        editor.fold_buffer(buffer_ids[1], cx);
29652    });
29653    cx.assert_excerpts_with_selections(indoc! {"
29654        [EXCERPT]
29655        [FOLDED]
29656        [EXCERPT]
29657        ˇ[FOLDED]
29658        "});
29659
29660    // Test selection inside folded buffer unfolds it on type
29661    cx.update_editor(|editor, window, cx| {
29662        editor.handle_input("W", window, cx);
29663    });
29664    cx.update_editor(|editor, _, cx| {
29665        editor.unfold_buffer(buffer_ids[0], cx);
29666    });
29667    cx.assert_excerpts_with_selections(indoc! {"
29668        [EXCERPT]
29669        1
29670        2
29671        3
29672        [EXCERPT]
29673        Wˇ1
29674        Z
29675        3
29676        "});
29677}
29678
29679#[gpui::test]
29680async fn test_multibuffer_scroll_cursor_top_margin(cx: &mut TestAppContext) {
29681    init_test(cx, |_| {});
29682
29683    let (editor, cx) = cx.add_window_view(|window, cx| {
29684        let multi_buffer = MultiBuffer::build_multi(
29685            [
29686                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
29687                ("1\n2\n3\n4\n5\n6\n7\n8\n9\n", vec![Point::row_range(0..9)]),
29688            ],
29689            cx,
29690        );
29691        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
29692    });
29693
29694    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
29695
29696    cx.assert_excerpts_with_selections(indoc! {"
29697        [EXCERPT]
29698        ˇ1
29699        2
29700        3
29701        [EXCERPT]
29702        1
29703        2
29704        3
29705        4
29706        5
29707        6
29708        7
29709        8
29710        9
29711        "});
29712
29713    cx.update_editor(|editor, window, cx| {
29714        editor.change_selections(None.into(), window, cx, |s| {
29715            s.select_ranges([MultiBufferOffset(19)..MultiBufferOffset(19)]);
29716        });
29717    });
29718
29719    cx.assert_excerpts_with_selections(indoc! {"
29720        [EXCERPT]
29721        1
29722        2
29723        3
29724        [EXCERPT]
29725        1
29726        2
29727        3
29728        4
29729        5
29730        6
29731        ˇ7
29732        8
29733        9
29734        "});
29735
29736    cx.update_editor(|editor, _window, cx| {
29737        editor.set_vertical_scroll_margin(0, cx);
29738    });
29739
29740    cx.update_editor(|editor, window, cx| {
29741        assert_eq!(editor.vertical_scroll_margin(), 0);
29742        editor.scroll_cursor_top(&ScrollCursorTop, window, cx);
29743        assert_eq!(
29744            editor.snapshot(window, cx).scroll_position(),
29745            gpui::Point::new(0., 12.0)
29746        );
29747    });
29748
29749    cx.update_editor(|editor, _window, cx| {
29750        editor.set_vertical_scroll_margin(3, cx);
29751    });
29752
29753    cx.update_editor(|editor, window, cx| {
29754        assert_eq!(editor.vertical_scroll_margin(), 3);
29755        editor.scroll_cursor_top(&ScrollCursorTop, window, cx);
29756        assert_eq!(
29757            editor.snapshot(window, cx).scroll_position(),
29758            gpui::Point::new(0., 9.0)
29759        );
29760    });
29761}
29762
29763#[gpui::test]
29764async fn test_find_references_single_case(cx: &mut TestAppContext) {
29765    init_test(cx, |_| {});
29766    let mut cx = EditorLspTestContext::new_rust(
29767        lsp::ServerCapabilities {
29768            references_provider: Some(lsp::OneOf::Left(true)),
29769            ..lsp::ServerCapabilities::default()
29770        },
29771        cx,
29772    )
29773    .await;
29774
29775    let before = indoc!(
29776        r#"
29777        fn main() {
29778            let aˇbc = 123;
29779            let xyz = abc;
29780        }
29781        "#
29782    );
29783    let after = indoc!(
29784        r#"
29785        fn main() {
29786            let abc = 123;
29787            let xyz = ˇabc;
29788        }
29789        "#
29790    );
29791
29792    cx.lsp
29793        .set_request_handler::<lsp::request::References, _, _>(async move |params, _| {
29794            Ok(Some(vec![
29795                lsp::Location {
29796                    uri: params.text_document_position.text_document.uri.clone(),
29797                    range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 11)),
29798                },
29799                lsp::Location {
29800                    uri: params.text_document_position.text_document.uri,
29801                    range: lsp::Range::new(lsp::Position::new(2, 14), lsp::Position::new(2, 17)),
29802                },
29803            ]))
29804        });
29805
29806    cx.set_state(before);
29807
29808    let action = FindAllReferences {
29809        always_open_multibuffer: false,
29810    };
29811
29812    let navigated = cx
29813        .update_editor(|editor, window, cx| editor.find_all_references(&action, window, cx))
29814        .expect("should have spawned a task")
29815        .await
29816        .unwrap();
29817
29818    assert_eq!(navigated, Navigated::No);
29819
29820    cx.run_until_parked();
29821
29822    cx.assert_editor_state(after);
29823}
29824
29825#[gpui::test]
29826async fn test_newline_task_list_continuation(cx: &mut TestAppContext) {
29827    init_test(cx, |settings| {
29828        settings.defaults.tab_size = Some(2.try_into().unwrap());
29829    });
29830
29831    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
29832    let mut cx = EditorTestContext::new(cx).await;
29833    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
29834
29835    // Case 1: Adding newline after (whitespace + prefix + any non-whitespace) adds marker
29836    cx.set_state(indoc! {"
29837        - [ ] taskˇ
29838    "});
29839    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29840    cx.wait_for_autoindent_applied().await;
29841    cx.assert_editor_state(indoc! {"
29842        - [ ] task
29843        - [ ] ˇ
29844    "});
29845
29846    // Case 2: Works with checked task items too
29847    cx.set_state(indoc! {"
29848        - [x] completed taskˇ
29849    "});
29850    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29851    cx.wait_for_autoindent_applied().await;
29852    cx.assert_editor_state(indoc! {"
29853        - [x] completed task
29854        - [ ] ˇ
29855    "});
29856
29857    // Case 2.1: Works with uppercase checked marker too
29858    cx.set_state(indoc! {"
29859        - [X] completed taskˇ
29860    "});
29861    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29862    cx.wait_for_autoindent_applied().await;
29863    cx.assert_editor_state(indoc! {"
29864        - [X] completed task
29865        - [ ] ˇ
29866    "});
29867
29868    // Case 3: Cursor position doesn't matter - content after marker is what counts
29869    cx.set_state(indoc! {"
29870        - [ ] taˇsk
29871    "});
29872    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29873    cx.wait_for_autoindent_applied().await;
29874    cx.assert_editor_state(indoc! {"
29875        - [ ] ta
29876        - [ ] ˇsk
29877    "});
29878
29879    // Case 4: Adding newline after (whitespace + prefix + some whitespace) does NOT add marker
29880    cx.set_state(indoc! {"
29881        - [ ]  ˇ
29882    "});
29883    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29884    cx.wait_for_autoindent_applied().await;
29885    cx.assert_editor_state(
29886        indoc! {"
29887        - [ ]$$
29888        ˇ
29889    "}
29890        .replace("$", " ")
29891        .as_str(),
29892    );
29893
29894    // Case 5: Adding newline with content adds marker preserving indentation
29895    cx.set_state(indoc! {"
29896        - [ ] task
29897          - [ ] indentedˇ
29898    "});
29899    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29900    cx.wait_for_autoindent_applied().await;
29901    cx.assert_editor_state(indoc! {"
29902        - [ ] task
29903          - [ ] indented
29904          - [ ] ˇ
29905    "});
29906
29907    // Case 6: Adding newline with cursor right after prefix, unindents
29908    cx.set_state(indoc! {"
29909        - [ ] task
29910          - [ ] sub task
29911            - [ ] ˇ
29912    "});
29913    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29914    cx.wait_for_autoindent_applied().await;
29915    cx.assert_editor_state(indoc! {"
29916        - [ ] task
29917          - [ ] sub task
29918          - [ ] ˇ
29919    "});
29920    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29921    cx.wait_for_autoindent_applied().await;
29922
29923    // Case 7: Adding newline with cursor right after prefix, removes marker
29924    cx.assert_editor_state(indoc! {"
29925        - [ ] task
29926          - [ ] sub task
29927        - [ ] ˇ
29928    "});
29929    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29930    cx.wait_for_autoindent_applied().await;
29931    cx.assert_editor_state(indoc! {"
29932        - [ ] task
29933          - [ ] sub task
29934        ˇ
29935    "});
29936
29937    // Case 8: Cursor before or inside prefix does not add marker
29938    cx.set_state(indoc! {"
29939        ˇ- [ ] task
29940    "});
29941    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29942    cx.wait_for_autoindent_applied().await;
29943    cx.assert_editor_state(indoc! {"
29944
29945        ˇ- [ ] task
29946    "});
29947
29948    cx.set_state(indoc! {"
29949        - [ˇ ] task
29950    "});
29951    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29952    cx.wait_for_autoindent_applied().await;
29953    cx.assert_editor_state(indoc! {"
29954        - [
29955        ˇ
29956        ] task
29957    "});
29958}
29959
29960#[gpui::test]
29961async fn test_newline_unordered_list_continuation(cx: &mut TestAppContext) {
29962    init_test(cx, |settings| {
29963        settings.defaults.tab_size = Some(2.try_into().unwrap());
29964    });
29965
29966    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
29967    let mut cx = EditorTestContext::new(cx).await;
29968    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
29969
29970    // Case 1: Adding newline after (whitespace + marker + any non-whitespace) adds marker
29971    cx.set_state(indoc! {"
29972        - itemˇ
29973    "});
29974    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29975    cx.wait_for_autoindent_applied().await;
29976    cx.assert_editor_state(indoc! {"
29977        - item
29978        - ˇ
29979    "});
29980
29981    // Case 2: Works with different markers
29982    cx.set_state(indoc! {"
29983        * starred itemˇ
29984    "});
29985    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29986    cx.wait_for_autoindent_applied().await;
29987    cx.assert_editor_state(indoc! {"
29988        * starred item
29989        * ˇ
29990    "});
29991
29992    cx.set_state(indoc! {"
29993        + plus itemˇ
29994    "});
29995    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29996    cx.wait_for_autoindent_applied().await;
29997    cx.assert_editor_state(indoc! {"
29998        + plus item
29999        + ˇ
30000    "});
30001
30002    // Case 3: Cursor position doesn't matter - content after marker is what counts
30003    cx.set_state(indoc! {"
30004        - itˇem
30005    "});
30006    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30007    cx.wait_for_autoindent_applied().await;
30008    cx.assert_editor_state(indoc! {"
30009        - it
30010        - ˇem
30011    "});
30012
30013    // Case 4: Adding newline after (whitespace + marker + some whitespace) does NOT add marker
30014    cx.set_state(indoc! {"
30015        -  ˇ
30016    "});
30017    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30018    cx.wait_for_autoindent_applied().await;
30019    cx.assert_editor_state(
30020        indoc! {"
30021        - $
30022        ˇ
30023    "}
30024        .replace("$", " ")
30025        .as_str(),
30026    );
30027
30028    // Case 5: Adding newline with content adds marker preserving indentation
30029    cx.set_state(indoc! {"
30030        - item
30031          - indentedˇ
30032    "});
30033    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30034    cx.wait_for_autoindent_applied().await;
30035    cx.assert_editor_state(indoc! {"
30036        - item
30037          - indented
30038          - ˇ
30039    "});
30040
30041    // Case 6: Adding newline with cursor right after marker, unindents
30042    cx.set_state(indoc! {"
30043        - item
30044          - sub item
30045            - ˇ
30046    "});
30047    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30048    cx.wait_for_autoindent_applied().await;
30049    cx.assert_editor_state(indoc! {"
30050        - item
30051          - sub item
30052          - ˇ
30053    "});
30054    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30055    cx.wait_for_autoindent_applied().await;
30056
30057    // Case 7: Adding newline with cursor right after marker, removes marker
30058    cx.assert_editor_state(indoc! {"
30059        - item
30060          - sub item
30061        - ˇ
30062    "});
30063    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30064    cx.wait_for_autoindent_applied().await;
30065    cx.assert_editor_state(indoc! {"
30066        - item
30067          - sub item
30068        ˇ
30069    "});
30070
30071    // Case 8: Cursor before or inside prefix does not add marker
30072    cx.set_state(indoc! {"
30073        ˇ- item
30074    "});
30075    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30076    cx.wait_for_autoindent_applied().await;
30077    cx.assert_editor_state(indoc! {"
30078
30079        ˇ- item
30080    "});
30081
30082    cx.set_state(indoc! {"
30083        -ˇ item
30084    "});
30085    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30086    cx.wait_for_autoindent_applied().await;
30087    cx.assert_editor_state(indoc! {"
30088        -
30089        ˇitem
30090    "});
30091}
30092
30093#[gpui::test]
30094async fn test_newline_ordered_list_continuation(cx: &mut TestAppContext) {
30095    init_test(cx, |settings| {
30096        settings.defaults.tab_size = Some(2.try_into().unwrap());
30097    });
30098
30099    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
30100    let mut cx = EditorTestContext::new(cx).await;
30101    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
30102
30103    // Case 1: Adding newline after (whitespace + marker + any non-whitespace) increments number
30104    cx.set_state(indoc! {"
30105        1. first itemˇ
30106    "});
30107    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30108    cx.wait_for_autoindent_applied().await;
30109    cx.assert_editor_state(indoc! {"
30110        1. first item
30111        2. ˇ
30112    "});
30113
30114    // Case 2: Works with larger numbers
30115    cx.set_state(indoc! {"
30116        10. tenth itemˇ
30117    "});
30118    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30119    cx.wait_for_autoindent_applied().await;
30120    cx.assert_editor_state(indoc! {"
30121        10. tenth item
30122        11. ˇ
30123    "});
30124
30125    // Case 3: Cursor position doesn't matter - content after marker is what counts
30126    cx.set_state(indoc! {"
30127        1. itˇem
30128    "});
30129    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30130    cx.wait_for_autoindent_applied().await;
30131    cx.assert_editor_state(indoc! {"
30132        1. it
30133        2. ˇem
30134    "});
30135
30136    // Case 4: Adding newline after (whitespace + marker + some whitespace) does NOT add marker
30137    cx.set_state(indoc! {"
30138        1.  ˇ
30139    "});
30140    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30141    cx.wait_for_autoindent_applied().await;
30142    cx.assert_editor_state(
30143        indoc! {"
30144        1. $
30145        ˇ
30146    "}
30147        .replace("$", " ")
30148        .as_str(),
30149    );
30150
30151    // Case 5: Adding newline with content adds marker preserving indentation
30152    cx.set_state(indoc! {"
30153        1. item
30154          2. indentedˇ
30155    "});
30156    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30157    cx.wait_for_autoindent_applied().await;
30158    cx.assert_editor_state(indoc! {"
30159        1. item
30160          2. indented
30161          3. ˇ
30162    "});
30163
30164    // Case 6: Adding newline with cursor right after marker, unindents
30165    cx.set_state(indoc! {"
30166        1. item
30167          2. sub item
30168            3. ˇ
30169    "});
30170    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30171    cx.wait_for_autoindent_applied().await;
30172    cx.assert_editor_state(indoc! {"
30173        1. item
30174          2. sub item
30175          1. ˇ
30176    "});
30177    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30178    cx.wait_for_autoindent_applied().await;
30179
30180    // Case 7: Adding newline with cursor right after marker, removes marker
30181    cx.assert_editor_state(indoc! {"
30182        1. item
30183          2. sub item
30184        1. ˇ
30185    "});
30186    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30187    cx.wait_for_autoindent_applied().await;
30188    cx.assert_editor_state(indoc! {"
30189        1. item
30190          2. sub item
30191        ˇ
30192    "});
30193
30194    // Case 8: Cursor before or inside prefix does not add marker
30195    cx.set_state(indoc! {"
30196        ˇ1. item
30197    "});
30198    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30199    cx.wait_for_autoindent_applied().await;
30200    cx.assert_editor_state(indoc! {"
30201
30202        ˇ1. item
30203    "});
30204
30205    cx.set_state(indoc! {"
30206        1ˇ. item
30207    "});
30208    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30209    cx.wait_for_autoindent_applied().await;
30210    cx.assert_editor_state(indoc! {"
30211        1
30212        ˇ. item
30213    "});
30214}
30215
30216#[gpui::test]
30217async fn test_newline_should_not_autoindent_ordered_list(cx: &mut TestAppContext) {
30218    init_test(cx, |settings| {
30219        settings.defaults.tab_size = Some(2.try_into().unwrap());
30220    });
30221
30222    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
30223    let mut cx = EditorTestContext::new(cx).await;
30224    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
30225
30226    // Case 1: Adding newline after (whitespace + marker + any non-whitespace) increments number
30227    cx.set_state(indoc! {"
30228        1. first item
30229          1. sub first item
30230          2. sub second item
30231          3. ˇ
30232    "});
30233    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30234    cx.wait_for_autoindent_applied().await;
30235    cx.assert_editor_state(indoc! {"
30236        1. first item
30237          1. sub first item
30238          2. sub second item
30239        1. ˇ
30240    "});
30241}
30242
30243#[gpui::test]
30244async fn test_tab_list_indent(cx: &mut TestAppContext) {
30245    init_test(cx, |settings| {
30246        settings.defaults.tab_size = Some(2.try_into().unwrap());
30247    });
30248
30249    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
30250    let mut cx = EditorTestContext::new(cx).await;
30251    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
30252
30253    // Case 1: Unordered list - cursor after prefix, adds indent before prefix
30254    cx.set_state(indoc! {"
30255        - ˇitem
30256    "});
30257    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
30258    cx.wait_for_autoindent_applied().await;
30259    let expected = indoc! {"
30260        $$- ˇitem
30261    "};
30262    cx.assert_editor_state(expected.replace("$", " ").as_str());
30263
30264    // Case 2: Task list - cursor after prefix
30265    cx.set_state(indoc! {"
30266        - [ ] ˇtask
30267    "});
30268    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
30269    cx.wait_for_autoindent_applied().await;
30270    let expected = indoc! {"
30271        $$- [ ] ˇtask
30272    "};
30273    cx.assert_editor_state(expected.replace("$", " ").as_str());
30274
30275    // Case 3: Ordered list - cursor after prefix
30276    cx.set_state(indoc! {"
30277        1. ˇfirst
30278    "});
30279    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
30280    cx.wait_for_autoindent_applied().await;
30281    let expected = indoc! {"
30282        $$1. ˇfirst
30283    "};
30284    cx.assert_editor_state(expected.replace("$", " ").as_str());
30285
30286    // Case 4: With existing indentation - adds more indent
30287    let initial = indoc! {"
30288        $$- ˇitem
30289    "};
30290    cx.set_state(initial.replace("$", " ").as_str());
30291    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
30292    cx.wait_for_autoindent_applied().await;
30293    let expected = indoc! {"
30294        $$$$- ˇitem
30295    "};
30296    cx.assert_editor_state(expected.replace("$", " ").as_str());
30297
30298    // Case 5: Empty list item
30299    cx.set_state(indoc! {"
30300        - ˇ
30301    "});
30302    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
30303    cx.wait_for_autoindent_applied().await;
30304    let expected = indoc! {"
30305        $$- ˇ
30306    "};
30307    cx.assert_editor_state(expected.replace("$", " ").as_str());
30308
30309    // Case 6: Cursor at end of line with content
30310    cx.set_state(indoc! {"
30311        - itemˇ
30312    "});
30313    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
30314    cx.wait_for_autoindent_applied().await;
30315    let expected = indoc! {"
30316        $$- itemˇ
30317    "};
30318    cx.assert_editor_state(expected.replace("$", " ").as_str());
30319
30320    // Case 7: Cursor at start of list item, indents it
30321    cx.set_state(indoc! {"
30322        - item
30323        ˇ  - sub item
30324    "});
30325    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
30326    cx.wait_for_autoindent_applied().await;
30327    let expected = indoc! {"
30328        - item
30329          ˇ  - sub item
30330    "};
30331    cx.assert_editor_state(expected);
30332
30333    // Case 8: Cursor at start of list item, moves the cursor when "indent_list_on_tab" is false
30334    cx.update_editor(|_, _, cx| {
30335        SettingsStore::update_global(cx, |store, cx| {
30336            store.update_user_settings(cx, |settings| {
30337                settings.project.all_languages.defaults.indent_list_on_tab = Some(false);
30338            });
30339        });
30340    });
30341    cx.set_state(indoc! {"
30342        - item
30343        ˇ  - sub item
30344    "});
30345    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
30346    cx.wait_for_autoindent_applied().await;
30347    let expected = indoc! {"
30348        - item
30349          ˇ- sub item
30350    "};
30351    cx.assert_editor_state(expected);
30352}
30353
30354#[gpui::test]
30355async fn test_local_worktree_trust(cx: &mut TestAppContext) {
30356    init_test(cx, |_| {});
30357    cx.update(|cx| project::trusted_worktrees::init(HashMap::default(), cx));
30358
30359    cx.update(|cx| {
30360        SettingsStore::update_global(cx, |store, cx| {
30361            store.update_user_settings(cx, |settings| {
30362                settings.project.all_languages.defaults.inlay_hints =
30363                    Some(InlayHintSettingsContent {
30364                        enabled: Some(true),
30365                        ..InlayHintSettingsContent::default()
30366                    });
30367            });
30368        });
30369    });
30370
30371    let fs = FakeFs::new(cx.executor());
30372    fs.insert_tree(
30373        path!("/project"),
30374        json!({
30375            ".zed": {
30376                "settings.json": r#"{"languages":{"Rust":{"language_servers":["override-rust-analyzer"]}}}"#
30377            },
30378            "main.rs": "fn main() {}"
30379        }),
30380    )
30381    .await;
30382
30383    let lsp_inlay_hint_request_count = Arc::new(AtomicUsize::new(0));
30384    let server_name = "override-rust-analyzer";
30385    let project = Project::test_with_worktree_trust(fs, [path!("/project").as_ref()], cx).await;
30386
30387    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
30388    language_registry.add(rust_lang());
30389
30390    let capabilities = lsp::ServerCapabilities {
30391        inlay_hint_provider: Some(lsp::OneOf::Left(true)),
30392        ..lsp::ServerCapabilities::default()
30393    };
30394    let mut fake_language_servers = language_registry.register_fake_lsp(
30395        "Rust",
30396        FakeLspAdapter {
30397            name: server_name,
30398            capabilities,
30399            initializer: Some(Box::new({
30400                let lsp_inlay_hint_request_count = lsp_inlay_hint_request_count.clone();
30401                move |fake_server| {
30402                    let lsp_inlay_hint_request_count = lsp_inlay_hint_request_count.clone();
30403                    fake_server.set_request_handler::<lsp::request::InlayHintRequest, _, _>(
30404                        move |_params, _| {
30405                            lsp_inlay_hint_request_count.fetch_add(1, atomic::Ordering::Release);
30406                            async move {
30407                                Ok(Some(vec![lsp::InlayHint {
30408                                    position: lsp::Position::new(0, 0),
30409                                    label: lsp::InlayHintLabel::String("hint".to_string()),
30410                                    kind: None,
30411                                    text_edits: None,
30412                                    tooltip: None,
30413                                    padding_left: None,
30414                                    padding_right: None,
30415                                    data: None,
30416                                }]))
30417                            }
30418                        },
30419                    );
30420                }
30421            })),
30422            ..FakeLspAdapter::default()
30423        },
30424    );
30425
30426    cx.run_until_parked();
30427
30428    let worktree_id = project.read_with(cx, |project, cx| {
30429        project
30430            .worktrees(cx)
30431            .next()
30432            .map(|wt| wt.read(cx).id())
30433            .expect("should have a worktree")
30434    });
30435    let worktree_store = project.read_with(cx, |project, _| project.worktree_store());
30436
30437    let trusted_worktrees =
30438        cx.update(|cx| TrustedWorktrees::try_get_global(cx).expect("trust global should exist"));
30439
30440    let can_trust = trusted_worktrees.update(cx, |store, cx| {
30441        store.can_trust(&worktree_store, worktree_id, cx)
30442    });
30443    assert!(!can_trust, "worktree should be restricted initially");
30444
30445    let buffer_before_approval = project
30446        .update(cx, |project, cx| {
30447            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
30448        })
30449        .await
30450        .unwrap();
30451
30452    let (editor, cx) = cx.add_window_view(|window, cx| {
30453        Editor::new(
30454            EditorMode::full(),
30455            cx.new(|cx| MultiBuffer::singleton(buffer_before_approval.clone(), cx)),
30456            Some(project.clone()),
30457            window,
30458            cx,
30459        )
30460    });
30461    cx.run_until_parked();
30462    let fake_language_server = fake_language_servers.next();
30463
30464    cx.read(|cx| {
30465        let file = buffer_before_approval.read(cx).file();
30466        assert_eq!(
30467            language::language_settings::language_settings(Some("Rust".into()), file, cx)
30468                .language_servers,
30469            ["...".to_string()],
30470            "local .zed/settings.json must not apply before trust approval"
30471        )
30472    });
30473
30474    editor.update_in(cx, |editor, window, cx| {
30475        editor.handle_input("1", window, cx);
30476    });
30477    cx.run_until_parked();
30478    cx.executor()
30479        .advance_clock(std::time::Duration::from_secs(1));
30480    assert_eq!(
30481        lsp_inlay_hint_request_count.load(atomic::Ordering::Acquire),
30482        0,
30483        "inlay hints must not be queried before trust approval"
30484    );
30485
30486    trusted_worktrees.update(cx, |store, cx| {
30487        store.trust(
30488            &worktree_store,
30489            std::collections::HashSet::from_iter([PathTrust::Worktree(worktree_id)]),
30490            cx,
30491        );
30492    });
30493    cx.run_until_parked();
30494
30495    cx.read(|cx| {
30496        let file = buffer_before_approval.read(cx).file();
30497        assert_eq!(
30498            language::language_settings::language_settings(Some("Rust".into()), file, cx)
30499                .language_servers,
30500            ["override-rust-analyzer".to_string()],
30501            "local .zed/settings.json should apply after trust approval"
30502        )
30503    });
30504    let _fake_language_server = fake_language_server.await.unwrap();
30505    editor.update_in(cx, |editor, window, cx| {
30506        editor.handle_input("1", window, cx);
30507    });
30508    cx.run_until_parked();
30509    cx.executor()
30510        .advance_clock(std::time::Duration::from_secs(1));
30511    assert!(
30512        lsp_inlay_hint_request_count.load(atomic::Ordering::Acquire) > 0,
30513        "inlay hints should be queried after trust approval"
30514    );
30515
30516    let can_trust_after = trusted_worktrees.update(cx, |store, cx| {
30517        store.can_trust(&worktree_store, worktree_id, cx)
30518    });
30519    assert!(can_trust_after, "worktree should be trusted after trust()");
30520}
30521
30522#[gpui::test]
30523fn test_editor_rendering_when_positioned_above_viewport(cx: &mut TestAppContext) {
30524    // This test reproduces a bug where drawing an editor at a position above the viewport
30525    // (simulating what happens when an AutoHeight editor inside a List is scrolled past)
30526    // causes an infinite loop in blocks_in_range.
30527    //
30528    // The issue: when the editor's bounds.origin.y is very negative (above the viewport),
30529    // the content mask intersection produces visible_bounds with origin at the viewport top.
30530    // This makes clipped_top_in_lines very large, causing start_row to exceed max_row.
30531    // When blocks_in_range is called with start_row > max_row, the cursor seeks to the end
30532    // but the while loop after seek never terminates because cursor.next() is a no-op at end.
30533    init_test(cx, |_| {});
30534
30535    let window = cx.add_window(|_, _| gpui::Empty);
30536    let mut cx = VisualTestContext::from_window(*window, cx);
30537
30538    let buffer = cx.update(|_, cx| MultiBuffer::build_simple("a\nb\nc\nd\ne\nf\ng\nh\ni\nj\n", cx));
30539    let editor = cx.new_window_entity(|window, cx| build_editor(buffer, window, cx));
30540
30541    // Simulate a small viewport (500x500 pixels at origin 0,0)
30542    cx.simulate_resize(gpui::size(px(500.), px(500.)));
30543
30544    // Draw the editor at a very negative Y position, simulating an editor that's been
30545    // scrolled way above the visible viewport (like in a List that has scrolled past it).
30546    // The editor is 3000px tall but positioned at y=-10000, so it's entirely above the viewport.
30547    // This should NOT hang - it should just render nothing.
30548    cx.draw(
30549        gpui::point(px(0.), px(-10000.)),
30550        gpui::size(px(500.), px(3000.)),
30551        |_, _| editor.clone(),
30552    );
30553
30554    // If we get here without hanging, the test passes
30555}
30556
30557#[gpui::test]
30558async fn test_diff_review_indicator_created_on_gutter_hover(cx: &mut TestAppContext) {
30559    init_test(cx, |_| {});
30560
30561    let fs = FakeFs::new(cx.executor());
30562    fs.insert_tree(path!("/root"), json!({ "file.txt": "hello\nworld\n" }))
30563        .await;
30564
30565    let project = Project::test(fs, [path!("/root").as_ref()], cx).await;
30566    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
30567    let cx = &mut VisualTestContext::from_window(*workspace, cx);
30568
30569    let editor = workspace
30570        .update(cx, |workspace, window, cx| {
30571            workspace.open_abs_path(
30572                PathBuf::from(path!("/root/file.txt")),
30573                OpenOptions::default(),
30574                window,
30575                cx,
30576            )
30577        })
30578        .unwrap()
30579        .await
30580        .unwrap()
30581        .downcast::<Editor>()
30582        .unwrap();
30583
30584    // Enable diff review button mode
30585    editor.update(cx, |editor, cx| {
30586        editor.set_show_diff_review_button(true, cx);
30587    });
30588
30589    // Initially, no indicator should be present
30590    editor.update(cx, |editor, _cx| {
30591        assert!(
30592            editor.gutter_diff_review_indicator.0.is_none(),
30593            "Indicator should be None initially"
30594        );
30595    });
30596}
30597
30598#[gpui::test]
30599async fn test_diff_review_button_hidden_when_ai_disabled(cx: &mut TestAppContext) {
30600    init_test(cx, |_| {});
30601
30602    // Register DisableAiSettings and set disable_ai to true
30603    cx.update(|cx| {
30604        project::DisableAiSettings::register(cx);
30605        project::DisableAiSettings::override_global(
30606            project::DisableAiSettings { disable_ai: true },
30607            cx,
30608        );
30609    });
30610
30611    let fs = FakeFs::new(cx.executor());
30612    fs.insert_tree(path!("/root"), json!({ "file.txt": "hello\nworld\n" }))
30613        .await;
30614
30615    let project = Project::test(fs, [path!("/root").as_ref()], cx).await;
30616    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
30617    let cx = &mut VisualTestContext::from_window(*workspace, cx);
30618
30619    let editor = workspace
30620        .update(cx, |workspace, window, cx| {
30621            workspace.open_abs_path(
30622                PathBuf::from(path!("/root/file.txt")),
30623                OpenOptions::default(),
30624                window,
30625                cx,
30626            )
30627        })
30628        .unwrap()
30629        .await
30630        .unwrap()
30631        .downcast::<Editor>()
30632        .unwrap();
30633
30634    // Enable diff review button mode
30635    editor.update(cx, |editor, cx| {
30636        editor.set_show_diff_review_button(true, cx);
30637    });
30638
30639    // Verify AI is disabled
30640    cx.read(|cx| {
30641        assert!(
30642            project::DisableAiSettings::get_global(cx).disable_ai,
30643            "AI should be disabled"
30644        );
30645    });
30646
30647    // The indicator should not be created when AI is disabled
30648    // (The mouse_moved handler checks DisableAiSettings before creating the indicator)
30649    editor.update(cx, |editor, _cx| {
30650        assert!(
30651            editor.gutter_diff_review_indicator.0.is_none(),
30652            "Indicator should be None when AI is disabled"
30653        );
30654    });
30655}
30656
30657#[gpui::test]
30658async fn test_diff_review_button_shown_when_ai_enabled(cx: &mut TestAppContext) {
30659    init_test(cx, |_| {});
30660
30661    // Register DisableAiSettings and set disable_ai to false
30662    cx.update(|cx| {
30663        project::DisableAiSettings::register(cx);
30664        project::DisableAiSettings::override_global(
30665            project::DisableAiSettings { disable_ai: false },
30666            cx,
30667        );
30668    });
30669
30670    let fs = FakeFs::new(cx.executor());
30671    fs.insert_tree(path!("/root"), json!({ "file.txt": "hello\nworld\n" }))
30672        .await;
30673
30674    let project = Project::test(fs, [path!("/root").as_ref()], cx).await;
30675    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
30676    let cx = &mut VisualTestContext::from_window(*workspace, cx);
30677
30678    let editor = workspace
30679        .update(cx, |workspace, window, cx| {
30680            workspace.open_abs_path(
30681                PathBuf::from(path!("/root/file.txt")),
30682                OpenOptions::default(),
30683                window,
30684                cx,
30685            )
30686        })
30687        .unwrap()
30688        .await
30689        .unwrap()
30690        .downcast::<Editor>()
30691        .unwrap();
30692
30693    // Enable diff review button mode
30694    editor.update(cx, |editor, cx| {
30695        editor.set_show_diff_review_button(true, cx);
30696    });
30697
30698    // Verify AI is enabled
30699    cx.read(|cx| {
30700        assert!(
30701            !project::DisableAiSettings::get_global(cx).disable_ai,
30702            "AI should be enabled"
30703        );
30704    });
30705
30706    // The show_diff_review_button flag should be true
30707    editor.update(cx, |editor, _cx| {
30708        assert!(
30709            editor.show_diff_review_button(),
30710            "show_diff_review_button should be true"
30711        );
30712    });
30713}
30714
30715/// Helper function to create a DiffHunkKey for testing.
30716/// Uses Anchor::min() as a placeholder anchor since these tests don't need
30717/// real buffer positioning.
30718fn test_hunk_key(file_path: &str) -> DiffHunkKey {
30719    DiffHunkKey {
30720        file_path: if file_path.is_empty() {
30721            Arc::from(util::rel_path::RelPath::empty())
30722        } else {
30723            Arc::from(util::rel_path::RelPath::unix(file_path).unwrap())
30724        },
30725        hunk_start_anchor: Anchor::min(),
30726    }
30727}
30728
30729/// Helper function to create a DiffHunkKey with a specific anchor for testing.
30730fn test_hunk_key_with_anchor(file_path: &str, anchor: Anchor) -> DiffHunkKey {
30731    DiffHunkKey {
30732        file_path: if file_path.is_empty() {
30733            Arc::from(util::rel_path::RelPath::empty())
30734        } else {
30735            Arc::from(util::rel_path::RelPath::unix(file_path).unwrap())
30736        },
30737        hunk_start_anchor: anchor,
30738    }
30739}
30740
30741/// Helper function to add a review comment with default anchors for testing.
30742fn add_test_comment(
30743    editor: &mut Editor,
30744    key: DiffHunkKey,
30745    comment: &str,
30746    cx: &mut Context<Editor>,
30747) -> usize {
30748    editor.add_review_comment(key, comment.to_string(), Anchor::min()..Anchor::max(), cx)
30749}
30750
30751#[gpui::test]
30752fn test_review_comment_add_to_hunk(cx: &mut TestAppContext) {
30753    init_test(cx, |_| {});
30754
30755    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
30756
30757    _ = editor.update(cx, |editor: &mut Editor, _window, cx| {
30758        let key = test_hunk_key("");
30759
30760        let id = add_test_comment(editor, key.clone(), "Test comment", cx);
30761
30762        let snapshot = editor.buffer().read(cx).snapshot(cx);
30763        assert_eq!(editor.total_review_comment_count(), 1);
30764        assert_eq!(editor.hunk_comment_count(&key, &snapshot), 1);
30765
30766        let comments = editor.comments_for_hunk(&key, &snapshot);
30767        assert_eq!(comments.len(), 1);
30768        assert_eq!(comments[0].comment, "Test comment");
30769        assert_eq!(comments[0].id, id);
30770    });
30771}
30772
30773#[gpui::test]
30774fn test_review_comments_are_per_hunk(cx: &mut TestAppContext) {
30775    init_test(cx, |_| {});
30776
30777    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
30778
30779    _ = editor.update(cx, |editor: &mut Editor, _window, cx| {
30780        let snapshot = editor.buffer().read(cx).snapshot(cx);
30781        let anchor1 = snapshot.anchor_before(Point::new(0, 0));
30782        let anchor2 = snapshot.anchor_before(Point::new(0, 0));
30783        let key1 = test_hunk_key_with_anchor("file1.rs", anchor1);
30784        let key2 = test_hunk_key_with_anchor("file2.rs", anchor2);
30785
30786        add_test_comment(editor, key1.clone(), "Comment for file1", cx);
30787        add_test_comment(editor, key2.clone(), "Comment for file2", cx);
30788
30789        let snapshot = editor.buffer().read(cx).snapshot(cx);
30790        assert_eq!(editor.total_review_comment_count(), 2);
30791        assert_eq!(editor.hunk_comment_count(&key1, &snapshot), 1);
30792        assert_eq!(editor.hunk_comment_count(&key2, &snapshot), 1);
30793
30794        assert_eq!(
30795            editor.comments_for_hunk(&key1, &snapshot)[0].comment,
30796            "Comment for file1"
30797        );
30798        assert_eq!(
30799            editor.comments_for_hunk(&key2, &snapshot)[0].comment,
30800            "Comment for file2"
30801        );
30802    });
30803}
30804
30805#[gpui::test]
30806fn test_review_comment_remove(cx: &mut TestAppContext) {
30807    init_test(cx, |_| {});
30808
30809    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
30810
30811    _ = editor.update(cx, |editor: &mut Editor, _window, cx| {
30812        let key = test_hunk_key("");
30813
30814        let id = add_test_comment(editor, key, "To be removed", cx);
30815
30816        assert_eq!(editor.total_review_comment_count(), 1);
30817
30818        let removed = editor.remove_review_comment(id, cx);
30819        assert!(removed);
30820        assert_eq!(editor.total_review_comment_count(), 0);
30821
30822        // Try to remove again
30823        let removed_again = editor.remove_review_comment(id, cx);
30824        assert!(!removed_again);
30825    });
30826}
30827
30828#[gpui::test]
30829fn test_review_comment_update(cx: &mut TestAppContext) {
30830    init_test(cx, |_| {});
30831
30832    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
30833
30834    _ = editor.update(cx, |editor: &mut Editor, _window, cx| {
30835        let key = test_hunk_key("");
30836
30837        let id = add_test_comment(editor, key.clone(), "Original text", cx);
30838
30839        let updated = editor.update_review_comment(id, "Updated text".to_string(), cx);
30840        assert!(updated);
30841
30842        let snapshot = editor.buffer().read(cx).snapshot(cx);
30843        let comments = editor.comments_for_hunk(&key, &snapshot);
30844        assert_eq!(comments[0].comment, "Updated text");
30845        assert!(!comments[0].is_editing); // Should clear editing flag
30846    });
30847}
30848
30849#[gpui::test]
30850fn test_review_comment_take_all(cx: &mut TestAppContext) {
30851    init_test(cx, |_| {});
30852
30853    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
30854
30855    _ = editor.update(cx, |editor: &mut Editor, _window, cx| {
30856        let snapshot = editor.buffer().read(cx).snapshot(cx);
30857        let anchor1 = snapshot.anchor_before(Point::new(0, 0));
30858        let anchor2 = snapshot.anchor_before(Point::new(0, 0));
30859        let key1 = test_hunk_key_with_anchor("file1.rs", anchor1);
30860        let key2 = test_hunk_key_with_anchor("file2.rs", anchor2);
30861
30862        let id1 = add_test_comment(editor, key1.clone(), "Comment 1", cx);
30863        let id2 = add_test_comment(editor, key1.clone(), "Comment 2", cx);
30864        let id3 = add_test_comment(editor, key2.clone(), "Comment 3", cx);
30865
30866        // IDs should be sequential starting from 0
30867        assert_eq!(id1, 0);
30868        assert_eq!(id2, 1);
30869        assert_eq!(id3, 2);
30870
30871        assert_eq!(editor.total_review_comment_count(), 3);
30872
30873        let taken = editor.take_all_review_comments(cx);
30874
30875        // Should have 2 entries (one per hunk)
30876        assert_eq!(taken.len(), 2);
30877
30878        // Total comments should be 3
30879        let total: usize = taken
30880            .iter()
30881            .map(|(_, comments): &(DiffHunkKey, Vec<StoredReviewComment>)| comments.len())
30882            .sum();
30883        assert_eq!(total, 3);
30884
30885        // Storage should be empty
30886        assert_eq!(editor.total_review_comment_count(), 0);
30887
30888        // After taking all comments, ID counter should reset
30889        // New comments should get IDs starting from 0 again
30890        let new_id1 = add_test_comment(editor, key1, "New Comment 1", cx);
30891        let new_id2 = add_test_comment(editor, key2, "New Comment 2", cx);
30892
30893        assert_eq!(new_id1, 0, "ID counter should reset after take_all");
30894        assert_eq!(new_id2, 1, "IDs should be sequential after reset");
30895    });
30896}
30897
30898#[gpui::test]
30899fn test_diff_review_overlay_show_and_dismiss(cx: &mut TestAppContext) {
30900    init_test(cx, |_| {});
30901
30902    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
30903
30904    // Show overlay
30905    editor
30906        .update(cx, |editor, window, cx| {
30907            editor.show_diff_review_overlay(DisplayRow(0)..DisplayRow(0), window, cx);
30908        })
30909        .unwrap();
30910
30911    // Verify overlay is shown
30912    editor
30913        .update(cx, |editor, _window, cx| {
30914            assert!(!editor.diff_review_overlays.is_empty());
30915            assert_eq!(editor.diff_review_line_range(cx), Some((0, 0)));
30916            assert!(editor.diff_review_prompt_editor().is_some());
30917        })
30918        .unwrap();
30919
30920    // Dismiss overlay
30921    editor
30922        .update(cx, |editor, _window, cx| {
30923            editor.dismiss_all_diff_review_overlays(cx);
30924        })
30925        .unwrap();
30926
30927    // Verify overlay is dismissed
30928    editor
30929        .update(cx, |editor, _window, cx| {
30930            assert!(editor.diff_review_overlays.is_empty());
30931            assert_eq!(editor.diff_review_line_range(cx), None);
30932            assert!(editor.diff_review_prompt_editor().is_none());
30933        })
30934        .unwrap();
30935}
30936
30937#[gpui::test]
30938fn test_diff_review_overlay_dismiss_via_cancel(cx: &mut TestAppContext) {
30939    init_test(cx, |_| {});
30940
30941    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
30942
30943    // Show overlay
30944    editor
30945        .update(cx, |editor, window, cx| {
30946            editor.show_diff_review_overlay(DisplayRow(0)..DisplayRow(0), window, cx);
30947        })
30948        .unwrap();
30949
30950    // Verify overlay is shown
30951    editor
30952        .update(cx, |editor, _window, _cx| {
30953            assert!(!editor.diff_review_overlays.is_empty());
30954        })
30955        .unwrap();
30956
30957    // Dismiss via dismiss_menus_and_popups (which is called by cancel action)
30958    editor
30959        .update(cx, |editor, window, cx| {
30960            editor.dismiss_menus_and_popups(true, window, cx);
30961        })
30962        .unwrap();
30963
30964    // Verify overlay is dismissed
30965    editor
30966        .update(cx, |editor, _window, _cx| {
30967            assert!(editor.diff_review_overlays.is_empty());
30968        })
30969        .unwrap();
30970}
30971
30972#[gpui::test]
30973fn test_diff_review_empty_comment_not_submitted(cx: &mut TestAppContext) {
30974    init_test(cx, |_| {});
30975
30976    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
30977
30978    // Show overlay
30979    editor
30980        .update(cx, |editor, window, cx| {
30981            editor.show_diff_review_overlay(DisplayRow(0)..DisplayRow(0), window, cx);
30982        })
30983        .unwrap();
30984
30985    // Try to submit without typing anything (empty comment)
30986    editor
30987        .update(cx, |editor, window, cx| {
30988            editor.submit_diff_review_comment(window, cx);
30989        })
30990        .unwrap();
30991
30992    // Verify no comment was added
30993    editor
30994        .update(cx, |editor, _window, _cx| {
30995            assert_eq!(editor.total_review_comment_count(), 0);
30996        })
30997        .unwrap();
30998
30999    // Try to submit with whitespace-only comment
31000    editor
31001        .update(cx, |editor, window, cx| {
31002            if let Some(prompt_editor) = editor.diff_review_prompt_editor().cloned() {
31003                prompt_editor.update(cx, |pe, cx| {
31004                    pe.insert("   \n\t  ", window, cx);
31005                });
31006            }
31007            editor.submit_diff_review_comment(window, cx);
31008        })
31009        .unwrap();
31010
31011    // Verify still no comment was added
31012    editor
31013        .update(cx, |editor, _window, _cx| {
31014            assert_eq!(editor.total_review_comment_count(), 0);
31015        })
31016        .unwrap();
31017}
31018
31019#[gpui::test]
31020fn test_diff_review_inline_edit_flow(cx: &mut TestAppContext) {
31021    init_test(cx, |_| {});
31022
31023    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
31024
31025    // Add a comment directly
31026    let comment_id = editor
31027        .update(cx, |editor, _window, cx| {
31028            let key = test_hunk_key("");
31029            add_test_comment(editor, key, "Original comment", cx)
31030        })
31031        .unwrap();
31032
31033    // Set comment to editing mode
31034    editor
31035        .update(cx, |editor, _window, cx| {
31036            editor.set_comment_editing(comment_id, true, cx);
31037        })
31038        .unwrap();
31039
31040    // Verify editing flag is set
31041    editor
31042        .update(cx, |editor, _window, cx| {
31043            let key = test_hunk_key("");
31044            let snapshot = editor.buffer().read(cx).snapshot(cx);
31045            let comments = editor.comments_for_hunk(&key, &snapshot);
31046            assert_eq!(comments.len(), 1);
31047            assert!(comments[0].is_editing);
31048        })
31049        .unwrap();
31050
31051    // Update the comment
31052    editor
31053        .update(cx, |editor, _window, cx| {
31054            let updated =
31055                editor.update_review_comment(comment_id, "Updated comment".to_string(), cx);
31056            assert!(updated);
31057        })
31058        .unwrap();
31059
31060    // Verify comment was updated and editing flag is cleared
31061    editor
31062        .update(cx, |editor, _window, cx| {
31063            let key = test_hunk_key("");
31064            let snapshot = editor.buffer().read(cx).snapshot(cx);
31065            let comments = editor.comments_for_hunk(&key, &snapshot);
31066            assert_eq!(comments[0].comment, "Updated comment");
31067            assert!(!comments[0].is_editing);
31068        })
31069        .unwrap();
31070}
31071
31072#[gpui::test]
31073fn test_orphaned_comments_are_cleaned_up(cx: &mut TestAppContext) {
31074    init_test(cx, |_| {});
31075
31076    // Create an editor with some text
31077    let editor = cx.add_window(|window, cx| {
31078        let buffer = cx.new(|cx| Buffer::local("line 1\nline 2\nline 3\n", cx));
31079        let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
31080        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
31081    });
31082
31083    // Add a comment with an anchor on line 2
31084    editor
31085        .update(cx, |editor, _window, cx| {
31086            let snapshot = editor.buffer().read(cx).snapshot(cx);
31087            let anchor = snapshot.anchor_after(Point::new(1, 0)); // Line 2
31088            let key = DiffHunkKey {
31089                file_path: Arc::from(util::rel_path::RelPath::empty()),
31090                hunk_start_anchor: anchor,
31091            };
31092            editor.add_review_comment(key, "Comment on line 2".to_string(), anchor..anchor, cx);
31093            assert_eq!(editor.total_review_comment_count(), 1);
31094        })
31095        .unwrap();
31096
31097    // Delete all content (this should orphan the comment's anchor)
31098    editor
31099        .update(cx, |editor, window, cx| {
31100            editor.select_all(&SelectAll, window, cx);
31101            editor.insert("completely new content", window, cx);
31102        })
31103        .unwrap();
31104
31105    // Trigger cleanup
31106    editor
31107        .update(cx, |editor, _window, cx| {
31108            editor.cleanup_orphaned_review_comments(cx);
31109            // Comment should be removed because its anchor is invalid
31110            assert_eq!(editor.total_review_comment_count(), 0);
31111        })
31112        .unwrap();
31113}
31114
31115#[gpui::test]
31116fn test_orphaned_comments_cleanup_called_on_buffer_edit(cx: &mut TestAppContext) {
31117    init_test(cx, |_| {});
31118
31119    // Create an editor with some text
31120    let editor = cx.add_window(|window, cx| {
31121        let buffer = cx.new(|cx| Buffer::local("line 1\nline 2\nline 3\n", cx));
31122        let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
31123        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
31124    });
31125
31126    // Add a comment with an anchor on line 2
31127    editor
31128        .update(cx, |editor, _window, cx| {
31129            let snapshot = editor.buffer().read(cx).snapshot(cx);
31130            let anchor = snapshot.anchor_after(Point::new(1, 0)); // Line 2
31131            let key = DiffHunkKey {
31132                file_path: Arc::from(util::rel_path::RelPath::empty()),
31133                hunk_start_anchor: anchor,
31134            };
31135            editor.add_review_comment(key, "Comment on line 2".to_string(), anchor..anchor, cx);
31136            assert_eq!(editor.total_review_comment_count(), 1);
31137        })
31138        .unwrap();
31139
31140    // Edit the buffer - this should trigger cleanup via on_buffer_event
31141    // Delete all content which orphans the anchor
31142    editor
31143        .update(cx, |editor, window, cx| {
31144            editor.select_all(&SelectAll, window, cx);
31145            editor.insert("completely new content", window, cx);
31146            // The cleanup is called automatically in on_buffer_event when Edited fires
31147        })
31148        .unwrap();
31149
31150    // Verify cleanup happened automatically (not manually triggered)
31151    editor
31152        .update(cx, |editor, _window, _cx| {
31153            // Comment should be removed because its anchor became invalid
31154            // and cleanup was called automatically on buffer edit
31155            assert_eq!(editor.total_review_comment_count(), 0);
31156        })
31157        .unwrap();
31158}
31159
31160#[gpui::test]
31161fn test_comments_stored_for_multiple_hunks(cx: &mut TestAppContext) {
31162    init_test(cx, |_| {});
31163
31164    // This test verifies that comments can be stored for multiple different hunks
31165    // and that hunk_comment_count correctly identifies comments per hunk.
31166    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
31167
31168    _ = editor.update(cx, |editor, _window, cx| {
31169        let snapshot = editor.buffer().read(cx).snapshot(cx);
31170
31171        // Create two different hunk keys (simulating two different files)
31172        let anchor = snapshot.anchor_before(Point::new(0, 0));
31173        let key1 = DiffHunkKey {
31174            file_path: Arc::from(util::rel_path::RelPath::unix("file1.rs").unwrap()),
31175            hunk_start_anchor: anchor,
31176        };
31177        let key2 = DiffHunkKey {
31178            file_path: Arc::from(util::rel_path::RelPath::unix("file2.rs").unwrap()),
31179            hunk_start_anchor: anchor,
31180        };
31181
31182        // Add comments to first hunk
31183        editor.add_review_comment(
31184            key1.clone(),
31185            "Comment 1 for file1".to_string(),
31186            anchor..anchor,
31187            cx,
31188        );
31189        editor.add_review_comment(
31190            key1.clone(),
31191            "Comment 2 for file1".to_string(),
31192            anchor..anchor,
31193            cx,
31194        );
31195
31196        // Add comment to second hunk
31197        editor.add_review_comment(
31198            key2.clone(),
31199            "Comment for file2".to_string(),
31200            anchor..anchor,
31201            cx,
31202        );
31203
31204        // Verify total count
31205        assert_eq!(editor.total_review_comment_count(), 3);
31206
31207        // Verify per-hunk counts
31208        let snapshot = editor.buffer().read(cx).snapshot(cx);
31209        assert_eq!(
31210            editor.hunk_comment_count(&key1, &snapshot),
31211            2,
31212            "file1 should have 2 comments"
31213        );
31214        assert_eq!(
31215            editor.hunk_comment_count(&key2, &snapshot),
31216            1,
31217            "file2 should have 1 comment"
31218        );
31219
31220        // Verify comments_for_hunk returns correct comments
31221        let file1_comments = editor.comments_for_hunk(&key1, &snapshot);
31222        assert_eq!(file1_comments.len(), 2);
31223        assert_eq!(file1_comments[0].comment, "Comment 1 for file1");
31224        assert_eq!(file1_comments[1].comment, "Comment 2 for file1");
31225
31226        let file2_comments = editor.comments_for_hunk(&key2, &snapshot);
31227        assert_eq!(file2_comments.len(), 1);
31228        assert_eq!(file2_comments[0].comment, "Comment for file2");
31229    });
31230}
31231
31232#[gpui::test]
31233fn test_same_hunk_detected_by_matching_keys(cx: &mut TestAppContext) {
31234    init_test(cx, |_| {});
31235
31236    // This test verifies that hunk_keys_match correctly identifies when two
31237    // DiffHunkKeys refer to the same hunk (same file path and anchor point).
31238    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
31239
31240    _ = editor.update(cx, |editor, _window, cx| {
31241        let snapshot = editor.buffer().read(cx).snapshot(cx);
31242        let anchor = snapshot.anchor_before(Point::new(0, 0));
31243
31244        // Create two keys with the same file path and anchor
31245        let key1 = DiffHunkKey {
31246            file_path: Arc::from(util::rel_path::RelPath::unix("file.rs").unwrap()),
31247            hunk_start_anchor: anchor,
31248        };
31249        let key2 = DiffHunkKey {
31250            file_path: Arc::from(util::rel_path::RelPath::unix("file.rs").unwrap()),
31251            hunk_start_anchor: anchor,
31252        };
31253
31254        // Add comment to first key
31255        editor.add_review_comment(key1, "Test comment".to_string(), anchor..anchor, cx);
31256
31257        // Verify second key (same hunk) finds the comment
31258        let snapshot = editor.buffer().read(cx).snapshot(cx);
31259        assert_eq!(
31260            editor.hunk_comment_count(&key2, &snapshot),
31261            1,
31262            "Same hunk should find the comment"
31263        );
31264
31265        // Create a key with different file path
31266        let different_file_key = DiffHunkKey {
31267            file_path: Arc::from(util::rel_path::RelPath::unix("other.rs").unwrap()),
31268            hunk_start_anchor: anchor,
31269        };
31270
31271        // Different file should not find the comment
31272        assert_eq!(
31273            editor.hunk_comment_count(&different_file_key, &snapshot),
31274            0,
31275            "Different file should not find the comment"
31276        );
31277    });
31278}
31279
31280#[gpui::test]
31281fn test_overlay_comments_expanded_state(cx: &mut TestAppContext) {
31282    init_test(cx, |_| {});
31283
31284    // This test verifies that set_diff_review_comments_expanded correctly
31285    // updates the expanded state of overlays.
31286    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
31287
31288    // Show overlay
31289    editor
31290        .update(cx, |editor, window, cx| {
31291            editor.show_diff_review_overlay(DisplayRow(0)..DisplayRow(0), window, cx);
31292        })
31293        .unwrap();
31294
31295    // Verify initially expanded (default)
31296    editor
31297        .update(cx, |editor, _window, _cx| {
31298            assert!(
31299                editor.diff_review_overlays[0].comments_expanded,
31300                "Should be expanded by default"
31301            );
31302        })
31303        .unwrap();
31304
31305    // Set to collapsed using the public method
31306    editor
31307        .update(cx, |editor, _window, cx| {
31308            editor.set_diff_review_comments_expanded(false, cx);
31309        })
31310        .unwrap();
31311
31312    // Verify collapsed
31313    editor
31314        .update(cx, |editor, _window, _cx| {
31315            assert!(
31316                !editor.diff_review_overlays[0].comments_expanded,
31317                "Should be collapsed after setting to false"
31318            );
31319        })
31320        .unwrap();
31321
31322    // Set back to expanded
31323    editor
31324        .update(cx, |editor, _window, cx| {
31325            editor.set_diff_review_comments_expanded(true, cx);
31326        })
31327        .unwrap();
31328
31329    // Verify expanded again
31330    editor
31331        .update(cx, |editor, _window, _cx| {
31332            assert!(
31333                editor.diff_review_overlays[0].comments_expanded,
31334                "Should be expanded after setting to true"
31335            );
31336        })
31337        .unwrap();
31338}
31339
31340#[gpui::test]
31341fn test_diff_review_multiline_selection(cx: &mut TestAppContext) {
31342    init_test(cx, |_| {});
31343
31344    // Create an editor with multiple lines of text
31345    let editor = cx.add_window(|window, cx| {
31346        let buffer = cx.new(|cx| Buffer::local("line 1\nline 2\nline 3\nline 4\nline 5\n", cx));
31347        let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
31348        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
31349    });
31350
31351    // Test showing overlay with a multi-line selection (lines 1-3, which are rows 0-2)
31352    editor
31353        .update(cx, |editor, window, cx| {
31354            editor.show_diff_review_overlay(DisplayRow(0)..DisplayRow(2), window, cx);
31355        })
31356        .unwrap();
31357
31358    // Verify line range
31359    editor
31360        .update(cx, |editor, _window, cx| {
31361            assert!(!editor.diff_review_overlays.is_empty());
31362            assert_eq!(editor.diff_review_line_range(cx), Some((0, 2)));
31363        })
31364        .unwrap();
31365
31366    // Dismiss and test with reversed range (end < start)
31367    editor
31368        .update(cx, |editor, _window, cx| {
31369            editor.dismiss_all_diff_review_overlays(cx);
31370        })
31371        .unwrap();
31372
31373    // Show overlay with reversed range - should normalize it
31374    editor
31375        .update(cx, |editor, window, cx| {
31376            editor.show_diff_review_overlay(DisplayRow(3)..DisplayRow(1), window, cx);
31377        })
31378        .unwrap();
31379
31380    // Verify range is normalized (start <= end)
31381    editor
31382        .update(cx, |editor, _window, cx| {
31383            assert_eq!(editor.diff_review_line_range(cx), Some((1, 3)));
31384        })
31385        .unwrap();
31386}
31387
31388#[gpui::test]
31389fn test_diff_review_drag_state(cx: &mut TestAppContext) {
31390    init_test(cx, |_| {});
31391
31392    let editor = cx.add_window(|window, cx| {
31393        let buffer = cx.new(|cx| Buffer::local("line 1\nline 2\nline 3\n", cx));
31394        let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
31395        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
31396    });
31397
31398    // Initially no drag state
31399    editor
31400        .update(cx, |editor, _window, _cx| {
31401            assert!(editor.diff_review_drag_state.is_none());
31402        })
31403        .unwrap();
31404
31405    // Start drag at row 1
31406    editor
31407        .update(cx, |editor, window, cx| {
31408            editor.start_diff_review_drag(DisplayRow(1), window, cx);
31409        })
31410        .unwrap();
31411
31412    // Verify drag state is set
31413    editor
31414        .update(cx, |editor, window, cx| {
31415            assert!(editor.diff_review_drag_state.is_some());
31416            let snapshot = editor.snapshot(window, cx);
31417            let range = editor
31418                .diff_review_drag_state
31419                .as_ref()
31420                .unwrap()
31421                .row_range(&snapshot.display_snapshot);
31422            assert_eq!(*range.start(), DisplayRow(1));
31423            assert_eq!(*range.end(), DisplayRow(1));
31424        })
31425        .unwrap();
31426
31427    // Update drag to row 3
31428    editor
31429        .update(cx, |editor, window, cx| {
31430            editor.update_diff_review_drag(DisplayRow(3), window, cx);
31431        })
31432        .unwrap();
31433
31434    // Verify drag state is updated
31435    editor
31436        .update(cx, |editor, window, cx| {
31437            assert!(editor.diff_review_drag_state.is_some());
31438            let snapshot = editor.snapshot(window, cx);
31439            let range = editor
31440                .diff_review_drag_state
31441                .as_ref()
31442                .unwrap()
31443                .row_range(&snapshot.display_snapshot);
31444            assert_eq!(*range.start(), DisplayRow(1));
31445            assert_eq!(*range.end(), DisplayRow(3));
31446        })
31447        .unwrap();
31448
31449    // End drag - should show overlay
31450    editor
31451        .update(cx, |editor, window, cx| {
31452            editor.end_diff_review_drag(window, cx);
31453        })
31454        .unwrap();
31455
31456    // Verify drag state is cleared and overlay is shown
31457    editor
31458        .update(cx, |editor, _window, cx| {
31459            assert!(editor.diff_review_drag_state.is_none());
31460            assert!(!editor.diff_review_overlays.is_empty());
31461            assert_eq!(editor.diff_review_line_range(cx), Some((1, 3)));
31462        })
31463        .unwrap();
31464}
31465
31466#[gpui::test]
31467fn test_diff_review_drag_cancel(cx: &mut TestAppContext) {
31468    init_test(cx, |_| {});
31469
31470    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
31471
31472    // Start drag
31473    editor
31474        .update(cx, |editor, window, cx| {
31475            editor.start_diff_review_drag(DisplayRow(0), window, cx);
31476        })
31477        .unwrap();
31478
31479    // Verify drag state is set
31480    editor
31481        .update(cx, |editor, _window, _cx| {
31482            assert!(editor.diff_review_drag_state.is_some());
31483        })
31484        .unwrap();
31485
31486    // Cancel drag
31487    editor
31488        .update(cx, |editor, _window, cx| {
31489            editor.cancel_diff_review_drag(cx);
31490        })
31491        .unwrap();
31492
31493    // Verify drag state is cleared and no overlay was created
31494    editor
31495        .update(cx, |editor, _window, _cx| {
31496            assert!(editor.diff_review_drag_state.is_none());
31497            assert!(editor.diff_review_overlays.is_empty());
31498        })
31499        .unwrap();
31500}
31501
31502#[gpui::test]
31503fn test_calculate_overlay_height(cx: &mut TestAppContext) {
31504    init_test(cx, |_| {});
31505
31506    // This test verifies that calculate_overlay_height returns correct heights
31507    // based on comment count and expanded state.
31508    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
31509
31510    _ = editor.update(cx, |editor, _window, cx| {
31511        let snapshot = editor.buffer().read(cx).snapshot(cx);
31512        let anchor = snapshot.anchor_before(Point::new(0, 0));
31513        let key = DiffHunkKey {
31514            file_path: Arc::from(util::rel_path::RelPath::empty()),
31515            hunk_start_anchor: anchor,
31516        };
31517
31518        // No comments: base height of 2
31519        let height_no_comments = editor.calculate_overlay_height(&key, true, &snapshot);
31520        assert_eq!(
31521            height_no_comments, 2,
31522            "Base height should be 2 with no comments"
31523        );
31524
31525        // Add one comment
31526        editor.add_review_comment(key.clone(), "Comment 1".to_string(), anchor..anchor, cx);
31527
31528        let snapshot = editor.buffer().read(cx).snapshot(cx);
31529
31530        // With comments expanded: base (2) + header (1) + 2 per comment
31531        let height_expanded = editor.calculate_overlay_height(&key, true, &snapshot);
31532        assert_eq!(
31533            height_expanded,
31534            2 + 1 + 2, // base + header + 1 comment * 2
31535            "Height with 1 comment expanded"
31536        );
31537
31538        // With comments collapsed: base (2) + header (1)
31539        let height_collapsed = editor.calculate_overlay_height(&key, false, &snapshot);
31540        assert_eq!(
31541            height_collapsed,
31542            2 + 1, // base + header only
31543            "Height with comments collapsed"
31544        );
31545
31546        // Add more comments
31547        editor.add_review_comment(key.clone(), "Comment 2".to_string(), anchor..anchor, cx);
31548        editor.add_review_comment(key.clone(), "Comment 3".to_string(), anchor..anchor, cx);
31549
31550        let snapshot = editor.buffer().read(cx).snapshot(cx);
31551
31552        // With 3 comments expanded
31553        let height_3_expanded = editor.calculate_overlay_height(&key, true, &snapshot);
31554        assert_eq!(
31555            height_3_expanded,
31556            2 + 1 + (3 * 2), // base + header + 3 comments * 2
31557            "Height with 3 comments expanded"
31558        );
31559
31560        // Collapsed height stays the same regardless of comment count
31561        let height_3_collapsed = editor.calculate_overlay_height(&key, false, &snapshot);
31562        assert_eq!(
31563            height_3_collapsed,
31564            2 + 1, // base + header only
31565            "Height with 3 comments collapsed should be same as 1 comment collapsed"
31566        );
31567    });
31568}
31569
31570#[gpui::test]
31571async fn test_move_to_start_end_of_larger_syntax_node_single_cursor(cx: &mut TestAppContext) {
31572    init_test(cx, |_| {});
31573
31574    let language = Arc::new(Language::new(
31575        LanguageConfig::default(),
31576        Some(tree_sitter_rust::LANGUAGE.into()),
31577    ));
31578
31579    let text = r#"
31580        fn main() {
31581            let x = foo(1, 2);
31582        }
31583    "#
31584    .unindent();
31585
31586    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
31587    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
31588    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
31589
31590    editor
31591        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
31592        .await;
31593
31594    // Test case 1: Move to end of syntax nodes
31595    editor.update_in(cx, |editor, window, cx| {
31596        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
31597            s.select_display_ranges([
31598                DisplayPoint::new(DisplayRow(1), 16)..DisplayPoint::new(DisplayRow(1), 16)
31599            ]);
31600        });
31601    });
31602    editor.update(cx, |editor, cx| {
31603        assert_text_with_selections(
31604            editor,
31605            indoc! {r#"
31606                fn main() {
31607                    let x = foo(ˇ1, 2);
31608                }
31609            "#},
31610            cx,
31611        );
31612    });
31613    editor.update_in(cx, |editor, window, cx| {
31614        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
31615    });
31616    editor.update(cx, |editor, cx| {
31617        assert_text_with_selections(
31618            editor,
31619            indoc! {r#"
31620                fn main() {
31621                    let x = foo(1ˇ, 2);
31622                }
31623            "#},
31624            cx,
31625        );
31626    });
31627    editor.update_in(cx, |editor, window, cx| {
31628        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
31629    });
31630    editor.update(cx, |editor, cx| {
31631        assert_text_with_selections(
31632            editor,
31633            indoc! {r#"
31634                fn main() {
31635                    let x = foo(1, 2)ˇ;
31636                }
31637            "#},
31638            cx,
31639        );
31640    });
31641    editor.update_in(cx, |editor, window, cx| {
31642        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
31643    });
31644    editor.update(cx, |editor, cx| {
31645        assert_text_with_selections(
31646            editor,
31647            indoc! {r#"
31648                fn main() {
31649                    let x = foo(1, 2);ˇ
31650                }
31651            "#},
31652            cx,
31653        );
31654    });
31655    editor.update_in(cx, |editor, window, cx| {
31656        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
31657    });
31658    editor.update(cx, |editor, cx| {
31659        assert_text_with_selections(
31660            editor,
31661            indoc! {r#"
31662                fn main() {
31663                    let x = foo(1, 2);
3166431665            "#},
31666            cx,
31667        );
31668    });
31669
31670    // Test case 2: Move to start of syntax nodes
31671    editor.update_in(cx, |editor, window, cx| {
31672        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
31673            s.select_display_ranges([
31674                DisplayPoint::new(DisplayRow(1), 20)..DisplayPoint::new(DisplayRow(1), 20)
31675            ]);
31676        });
31677    });
31678    editor.update(cx, |editor, cx| {
31679        assert_text_with_selections(
31680            editor,
31681            indoc! {r#"
31682                fn main() {
31683                    let x = foo(1, 2ˇ);
31684                }
31685            "#},
31686            cx,
31687        );
31688    });
31689    editor.update_in(cx, |editor, window, cx| {
31690        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
31691    });
31692    editor.update(cx, |editor, cx| {
31693        assert_text_with_selections(
31694            editor,
31695            indoc! {r#"
31696                fn main() {
31697                    let x = fooˇ(1, 2);
31698                }
31699            "#},
31700            cx,
31701        );
31702    });
31703    editor.update_in(cx, |editor, window, cx| {
31704        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
31705    });
31706    editor.update(cx, |editor, cx| {
31707        assert_text_with_selections(
31708            editor,
31709            indoc! {r#"
31710                fn main() {
31711                    let x = ˇfoo(1, 2);
31712                }
31713            "#},
31714            cx,
31715        );
31716    });
31717    editor.update_in(cx, |editor, window, cx| {
31718        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
31719    });
31720    editor.update(cx, |editor, cx| {
31721        assert_text_with_selections(
31722            editor,
31723            indoc! {r#"
31724                fn main() {
31725                    ˇlet x = foo(1, 2);
31726                }
31727            "#},
31728            cx,
31729        );
31730    });
31731    editor.update_in(cx, |editor, window, cx| {
31732        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
31733    });
31734    editor.update(cx, |editor, cx| {
31735        assert_text_with_selections(
31736            editor,
31737            indoc! {r#"
31738                fn main() ˇ{
31739                    let x = foo(1, 2);
31740                }
31741            "#},
31742            cx,
31743        );
31744    });
31745    editor.update_in(cx, |editor, window, cx| {
31746        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
31747    });
31748    editor.update(cx, |editor, cx| {
31749        assert_text_with_selections(
31750            editor,
31751            indoc! {r#"
31752                ˇfn main() {
31753                    let x = foo(1, 2);
31754                }
31755            "#},
31756            cx,
31757        );
31758    });
31759}
31760
31761#[gpui::test]
31762async fn test_move_to_start_end_of_larger_syntax_node_two_cursors(cx: &mut TestAppContext) {
31763    init_test(cx, |_| {});
31764
31765    let language = Arc::new(Language::new(
31766        LanguageConfig::default(),
31767        Some(tree_sitter_rust::LANGUAGE.into()),
31768    ));
31769
31770    let text = r#"
31771        fn main() {
31772            let x = foo(1, 2);
31773            let y = bar(3, 4);
31774        }
31775    "#
31776    .unindent();
31777
31778    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
31779    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
31780    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
31781
31782    editor
31783        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
31784        .await;
31785
31786    // Test case 1: Move to end of syntax nodes with two cursors
31787    editor.update_in(cx, |editor, window, cx| {
31788        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
31789            s.select_display_ranges([
31790                DisplayPoint::new(DisplayRow(1), 20)..DisplayPoint::new(DisplayRow(1), 20),
31791                DisplayPoint::new(DisplayRow(2), 20)..DisplayPoint::new(DisplayRow(2), 20),
31792            ]);
31793        });
31794    });
31795    editor.update(cx, |editor, cx| {
31796        assert_text_with_selections(
31797            editor,
31798            indoc! {r#"
31799                fn main() {
31800                    let x = foo(1, 2ˇ);
31801                    let y = bar(3, 4ˇ);
31802                }
31803            "#},
31804            cx,
31805        );
31806    });
31807    editor.update_in(cx, |editor, window, cx| {
31808        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
31809    });
31810    editor.update(cx, |editor, cx| {
31811        assert_text_with_selections(
31812            editor,
31813            indoc! {r#"
31814                fn main() {
31815                    let x = foo(1, 2)ˇ;
31816                    let y = bar(3, 4)ˇ;
31817                }
31818            "#},
31819            cx,
31820        );
31821    });
31822    editor.update_in(cx, |editor, window, cx| {
31823        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
31824    });
31825    editor.update(cx, |editor, cx| {
31826        assert_text_with_selections(
31827            editor,
31828            indoc! {r#"
31829                fn main() {
31830                    let x = foo(1, 2);ˇ
31831                    let y = bar(3, 4);ˇ
31832                }
31833            "#},
31834            cx,
31835        );
31836    });
31837
31838    // Test case 2: Move to start of syntax nodes with two cursors
31839    editor.update_in(cx, |editor, window, cx| {
31840        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
31841            s.select_display_ranges([
31842                DisplayPoint::new(DisplayRow(1), 19)..DisplayPoint::new(DisplayRow(1), 19),
31843                DisplayPoint::new(DisplayRow(2), 19)..DisplayPoint::new(DisplayRow(2), 19),
31844            ]);
31845        });
31846    });
31847    editor.update(cx, |editor, cx| {
31848        assert_text_with_selections(
31849            editor,
31850            indoc! {r#"
31851                fn main() {
31852                    let x = foo(1, ˇ2);
31853                    let y = bar(3, ˇ4);
31854                }
31855            "#},
31856            cx,
31857        );
31858    });
31859    editor.update_in(cx, |editor, window, cx| {
31860        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
31861    });
31862    editor.update(cx, |editor, cx| {
31863        assert_text_with_selections(
31864            editor,
31865            indoc! {r#"
31866                fn main() {
31867                    let x = fooˇ(1, 2);
31868                    let y = barˇ(3, 4);
31869                }
31870            "#},
31871            cx,
31872        );
31873    });
31874    editor.update_in(cx, |editor, window, cx| {
31875        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
31876    });
31877    editor.update(cx, |editor, cx| {
31878        assert_text_with_selections(
31879            editor,
31880            indoc! {r#"
31881                fn main() {
31882                    let x = ˇfoo(1, 2);
31883                    let y = ˇbar(3, 4);
31884                }
31885            "#},
31886            cx,
31887        );
31888    });
31889    editor.update_in(cx, |editor, window, cx| {
31890        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
31891    });
31892    editor.update(cx, |editor, cx| {
31893        assert_text_with_selections(
31894            editor,
31895            indoc! {r#"
31896                fn main() {
31897                    ˇlet x = foo(1, 2);
31898                    ˇlet y = bar(3, 4);
31899                }
31900            "#},
31901            cx,
31902        );
31903    });
31904}
31905
31906#[gpui::test]
31907async fn test_move_to_start_end_of_larger_syntax_node_with_selections_and_strings(
31908    cx: &mut TestAppContext,
31909) {
31910    init_test(cx, |_| {});
31911
31912    let language = Arc::new(Language::new(
31913        LanguageConfig::default(),
31914        Some(tree_sitter_rust::LANGUAGE.into()),
31915    ));
31916
31917    let text = r#"
31918        fn main() {
31919            let x = foo(1, 2);
31920            let msg = "hello world";
31921        }
31922    "#
31923    .unindent();
31924
31925    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
31926    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
31927    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
31928
31929    editor
31930        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
31931        .await;
31932
31933    // Test case 1: With existing selection, move_to_end keeps selection
31934    editor.update_in(cx, |editor, window, cx| {
31935        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
31936            s.select_display_ranges([
31937                DisplayPoint::new(DisplayRow(1), 12)..DisplayPoint::new(DisplayRow(1), 21)
31938            ]);
31939        });
31940    });
31941    editor.update(cx, |editor, cx| {
31942        assert_text_with_selections(
31943            editor,
31944            indoc! {r#"
31945                fn main() {
31946                    let x = «foo(1, 2)ˇ»;
31947                    let msg = "hello world";
31948                }
31949            "#},
31950            cx,
31951        );
31952    });
31953    editor.update_in(cx, |editor, window, cx| {
31954        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
31955    });
31956    editor.update(cx, |editor, cx| {
31957        assert_text_with_selections(
31958            editor,
31959            indoc! {r#"
31960                fn main() {
31961                    let x = «foo(1, 2)ˇ»;
31962                    let msg = "hello world";
31963                }
31964            "#},
31965            cx,
31966        );
31967    });
31968
31969    // Test case 2: Move to end within a string
31970    editor.update_in(cx, |editor, window, cx| {
31971        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
31972            s.select_display_ranges([
31973                DisplayPoint::new(DisplayRow(2), 15)..DisplayPoint::new(DisplayRow(2), 15)
31974            ]);
31975        });
31976    });
31977    editor.update(cx, |editor, cx| {
31978        assert_text_with_selections(
31979            editor,
31980            indoc! {r#"
31981                fn main() {
31982                    let x = foo(1, 2);
31983                    let msg = "ˇhello world";
31984                }
31985            "#},
31986            cx,
31987        );
31988    });
31989    editor.update_in(cx, |editor, window, cx| {
31990        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
31991    });
31992    editor.update(cx, |editor, cx| {
31993        assert_text_with_selections(
31994            editor,
31995            indoc! {r#"
31996                fn main() {
31997                    let x = foo(1, 2);
31998                    let msg = "hello worldˇ";
31999                }
32000            "#},
32001            cx,
32002        );
32003    });
32004
32005    // Test case 3: Move to start within a string
32006    editor.update_in(cx, |editor, window, cx| {
32007        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
32008            s.select_display_ranges([
32009                DisplayPoint::new(DisplayRow(2), 21)..DisplayPoint::new(DisplayRow(2), 21)
32010            ]);
32011        });
32012    });
32013    editor.update(cx, |editor, cx| {
32014        assert_text_with_selections(
32015            editor,
32016            indoc! {r#"
32017                fn main() {
32018                    let x = foo(1, 2);
32019                    let msg = "hello ˇworld";
32020                }
32021            "#},
32022            cx,
32023        );
32024    });
32025    editor.update_in(cx, |editor, window, cx| {
32026        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
32027    });
32028    editor.update(cx, |editor, cx| {
32029        assert_text_with_selections(
32030            editor,
32031            indoc! {r#"
32032                fn main() {
32033                    let x = foo(1, 2);
32034                    let msg = "ˇhello world";
32035                }
32036            "#},
32037            cx,
32038        );
32039    });
32040}